- PREPRINT -

How to build a real-time prediction model for influenza in Germany

Paul Schneider, Maastricht University, Netherlands Institute of Health Service Research
David Barnett, Maastricht University
John Paget, Netherlands Institute of Health Service Research
Peter Spreeuwenberg, Netherlands Institute of Health Service Research
Christel van Gool, Maastricht University

Contact: schneider.paulpeter@gmail.com


Summary

This is a fully reproducable R-tutorial on how to build a (near) real-time prediction model for influenza in Germany, based on search query data from Google Trends and page view statistics from Wikipedia. All code and data is openly available. Instructions include guidance on how to identify and download relevant data, format and pre-process the retrieved information, and build and evaluate prediction models, using multiple statistical modelling techniques (a.k.a. machine learning). In addition, we will provide a short introduction to the emerging field of ‘Digital Epidemiology’ and discuss some of its critical methodological issues.

TL;DR: Code only version

Methods are inspired by the caret R-package and the book Applied Predictive Modelling, both by Max Kuhn.



****

Introduction

Digital Epidemiology

In the past, people could only went to see their doctor when they were sick and wanted to get information, guidance and treatment. Many national disease surveillance systems are therefore based on doctors as sentinels: health care providers and medical laboratories are being asked to report the number of cases for a certain disease to the surveillance organization. In Germany, for example, laboratories are required to report any proof of influenza viruses to the local health authority, which in turn reports it to the Robert Koch Institute. Nowadays, however, many people first turn to the internet. They seek health information by googling for their sysmptoms, look up diseases or treatments on Wikipedia or connect with others through social media. For Epidemiologists, this offers the opportunity to track these digital traces of people. This has been described as ‘Digital Epidemilogy’: “Digital data sources, when harnessed appropriately, can provide local and timely information about disease and health dynamics in populations around the world”.

Milestones of digital epidemiology

  • In 2008, Google Flu Trend was launched and accompanied by a broad media coverage. ..
  • In 2014, McIver and Brownstein claimed that Wikipedia page view data …
  • Twitter
  • Health Map
  • Influenza mao netherlands

Why Influenza?

In principle, the methods of surveillance, we use in this example, can be applied to any disease. However, some of the features of influenza make it easier to detect relevant predictors and monitor its activity. First, influenza has a strong seasonality (in most countries). Every year there is a start and an end, and during the summer months there are literally no cases of influenza. This strong annual pattern can be detected easily. In diseases, in which the incidence is more stable, there might be not enough variation to distinguish signal and noise. Second, during the flu season, between 5% and 20% of the population get sick. The online traces of rare diseases (say, achalasia) probably get lost in noise. And finally, influenza has distinct symptoms that most people will recognize as flu-symptoms, this means the symptoms are (somewhat) specific to influenza. In contrast, diseases with only subtle or very non-specific symptoms (say, fatigue) are certainly more problematic to track.

Nevertheless, there is no reason to assume that other diseases can not be surveyed using similar methods. A few papers have already shown promising results in …Ebola, TUberculosis… [ADD LINK][LINK]. We would indeed be very interested to see how predictive models perform in further diseases, like allergic rhinitis, depression or even sunburns. If you have good data on this or know where to get it, we invite you to follow this tutorial, build your own model(s) and share your results with us and the public.

Issues

The huge amount of available data and the novelity of methods are a challenge.
Overly optimistic because of

  • Variety of methods and data sources.
  • Inconsistent reporting of methods and results, the ai should be reproducability. In the area of digital epidemiology, this might imply open sharing of code and data- otherwise reproduciability is rarely possible.
  • The failure to strictly separate trainig and test data sets
    • Espeically in split-cross validation, leaking info from the future to the past
  • reporting of correlation coeficients, adjusted or not, or rmse, both?!
  • Research area mostly in the US (cite global paper), unclear whether methods are reproducible in countries in which digitalization is not that advanced.

Research within digital epi must adhere to the rigour and caution other epidemiological project are also held accountable to.



Tutorial

Overview

We will build a model for predicting the influenza incidence (lab-confirmed cases) in Germany in near real-time (‘Nowcasting’), by using data from various sources. We will start with getting information on influenza incidence in Germany, since the availability of the outcome data determines the scope and time horizon of the model we build. We then download data on search queries and page view statistics from Google Correlate, Google Trends and Wikipedia, respectively. The data have to be formatted, merged and pre-processed, before entering into the model. Subsequently, we will use various so called machine learning methods to select relevant predictors and build prediction models. Finally, we will compare the different models in terms of predictive accuracy and select the best model to predict this week’s influenza activity.

We would like to point out that when we speak of ‘predictions’, we use the term in its statistical sense. We do not mean ’forecasting, as in forecasting future influenza cases. We are doing ‘Nowcasting’: We observe what people were doing on the internet until today and infer from this information how many influenza cases there are about now

Data sources

Outome data is taken from the Robert Koch Institute, predictor data from Google Correlate, Google Trends and Wikipedia via it’s API and Wikishark. All analyses are performed using the statistical software R/R Studio, and plenty of its packages, particularly the caret package. Everything we use is available online for free. The data used in this example is also available on github. If you want to build your own model, you have to have a Google account to upload outcome data to Google Correlate.

Transferability

If you want to transfer this approach to another setting (another country or another disease), the only thing you need is a set of outcome data, i.e. good-quality data of the actual weekly incidence of the disease, over a sufficient time span (1 year +), in a specific country. The rest should work (more or less) automatically, unless you wish to add more data sources as predictors, which may require some more editing. Depending on the country in which you want to predict disease activity, the availability and quality of Google and Wikipedia data may differ substantially. For Wikipedia data, page view statistics can only be distinguished by language, but not by country. Language can be a good proxy in some cases (e.g. Japanese, Korean, Italian, etc.), but a pretty bad one in others (e.g. French, English, Spanish). You can look up how much of a country’s Wikipedia traffic is in a specfific language, and how much Wikipedia traffic within a specific language is from a specfific country. Moreover, with weekly data, you should be aware that there are divergent opinions about what is the best starting day for a week (Saturday? Sunday? Monday?).



Getting started

Before building the model, we need to install and load the required R-packages (yes, there are a lot)…

# Install and load all required packages
# This may take a while...
required_packages<-c("knitr","RCurl","ISOweek","jsonlite","ggplot2","prophet","dplyr","gtrendsR","wikipediatrend","pageviews","caret","imputeTS","gridExtra","cowplot","corrplot","doParallel","glmnet", "Cubist","pls","devtools","plotmo","plotrix","TeachingDemos","earth","kernlab","rpart","party","grid","zoo","mvtnorm","modeltools","stats4","strucchange","sandwich","elasticnet","lars","randomForest","ipred","e1071") # 
pft_packages <- function(package){
    for(i in 1:length(package)){
      if(eval(parse(text=paste("require(",package[i],")")))==0) {
        install.packages(package)}}
    return (eval(parse(text=paste("require(",package,")"))))}
pft_packages(required_packages)
#  gtrendR has to be the developer version. If you don't already have it, install it throughgithub:
#  devtools::install_github("PMassicotte/gtrendsR")
#  library(gtrendsR)


…and we should specify a few key parameters:

# What is the outcome of interest?  
term = "Influenza" #  laboratory-confirmed cases    
# For which country do we want to build the model? 
country_of_interest = "DE" # Germany in ISO_3166-2 (See: https://en.wikipedia.org/wiki/ISO_3166-2)  
# Which language is relevant?
language_of_interest = "de" # German in ISO_639-1 (See: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)  
# What the relevant time horizon (i.e. the time span we have data for)
from = as.Date("2010-07-31") # Start
to = as.Date("2017-07-31") # End   
# How do we split the data into training and test data?
split.at = as.Date("2016-08-01") 
# --> Training data:  2010-07-31 - 2016-08-01
# --> Test Data:      2016-08-02 - 2017-07-31


Get data

Outcome data

We can download German influenza incidence data (cases per per 10,000) from the Robert Koch Institute, from August 2010 until August 2017. We uploaded a spreadhseet to github, so it is easier to access. However, you can go to Survstat and customize your data query. The data do not come in the format we need, so we have to re-arrange it a bit.

# Load data from github repository
influenza.de = read.csv("https://raw.githubusercontent.com/projectflutrend/pft.2/master/outcome%20data/RKI.data.de.csv")
names(influenza.de) = c("date","y") 
head(influenza.de)
# Re-formatting 'date', and removing irrelevant data points
influenza.de$date = paste(sub("w","W", influenza.de$date),"-1", sep="")
influenza.de$date  = ISOweek2date(influenza.de$date)
influenza.de = influenza.de[influenza.de$date>as.Date("2010-07-31") & influenza.de$date<=as.Date("2017-07-31"),]
# The RKI data does not report data during summer months, in which there are literally no influenza cases. However, we want to have a model that predicts those zero-months as such, as well. Thus, we will fill the gap and set the cases for the inserted weeks to zero
reference =data.frame(date = seq(from=min(influenza.de$date),
                                 to=max(as.Date(influenza.de$date)),
                                 by=7))
influenza.de = merge(reference,influenza.de,by="date",all=T)
influenza.de$y[is.na(influenza.de$y)] = 0
# Plotting the data
ggplot(influenza.de,aes(x=date,y=y)) + 
  geom_line() + 
  theme_bw() + 
  geom_line() +
  annotate("rect", xmin=split.at, 
           xmax=max(influenza.de$date)+2, 
           ymin=min(influenza.de[,2])-2, 
           ymax=max(influenza.de$y), 
           fill="orange",
           alpha=0.2) +
  annotate("text", 
           x=median(influenza.de$date), 
           y=25, 
           label= paste("Split between training/test data:",split.at), 
           size=4,col="orange") +
  ylab("Incidence per 10,000") +
  ggtitle("Influenza in Germany")

The figure shows the influenza seasons 2010/2011 to 2016/2017, in Germany. More specifically, it shows ‘laboratory-confirmed cases’. It should be noted that this type of outcome is probably not ideal to use as a gold standard for training a digital surveillance system that is based on symptoms: If people feel like having the flu, they will look up influenza on Google and Wikipedia. Flu-like symptoms that occur during the summer months are, however, not caused by the influenza virus- this is why the Robert Koch Institute doesn’t report any data for these months and why we have to assume a flat line at zero. Our prediction model has to deal with the challenge of distinguishing between people who have the real influenza and people who only have influenza-like symptoms.

As a comparison: pattern of influenza-like illness in Germany, as reported by the Robert Koch Institute. Influenza-like illness is probably more appropriate as an outcome for nowcasting models, as it is a better reflection of the incidence of influenza-like symptoms. However, for Germany, this data is not publicly available.


The ‘null-model’

Before we go on to build a prediction model, we should quickly define what we will compare our model against. It would not be fair to compare a prediction model against a flat line at the mean, or at zero cases. This would be like a clinical trial with no comparator. So, let’s compare against placebo: Our model should at least be better than a ‘naiv’ forecast, which only knows what has happend in the past, and extrapolates the pattern into the future (a future that is the present to us). To build this Null-Model, we will use the prophet package that was build by people at Facebook. It is supposed to automatically detect changes in trends, fit a piecewise linear growth curve and add a yearly seasonal component, modeled using Fourier series.

For the present data, we will split the data set into training (seaons 2010/2011-2015/2016) and test data sets (season 2016/2017), we fit a forecast model using the training data and ask for a forecast for the next year. Subsequently, we can compare the predictions with the actual data from the test data set.

# Spliting the data for the forecast model into train and test data set
train.influenza.de = influenza.de[influenza.de$date<split.at,]
test.influenza.de = influenza.de[influenza.de$date>=split.at,]
# 'Propheting'
m <- prophet(df=data.frame(ds = train.influenza.de$date,
                           y=train.influenza.de$y),
             growth = "linear",
             yearly.seasonality = T,
             weekly.seasonality = F)
Initial log joint probability = -6.91857
Optimization terminated normally: 
  Convergence detected: relative gradient magnitude is below tolerance
future <- make_future_dataframe(m, periods = 365)
forecast <- predict(m, future)
p = plot(m, forecast) +
  geom_point(data=test.influenza.de, aes(x=date,y=y),col="pink",lty="dashed", size=2)
p

The prophet-forecast algorithm tries to find a pattern in the available data (2010/2011-2015/2016) and extrapolates it into the future (2016/2017). We can see that the 2016/2017 influenza season was unusually strong, compared to the last six seasons. The forecast model would have underpredicted the number of cases. Furthermore, the season started ealier than usual. So, the forecast model predicted the onset, peak and end of the season a few weeks earlier than they occured. Later, we can use this forecast and compare it to what our Nowcast-model predicts. There are probaly better models to forecast influenza, but it is still better than nothing.

Google Correlate

Google Correlate is a tool that can identify search queries that are highly correlated with any time series data you upload (Even for randomly generated numbers it will find a good match). It was a building block of Google Flu Trend. In addition, you can also identify queries that are correlated with another query: For example, find queries that are related to “Influenza” queries. See this paper for more info on how Google Correlate and Trends works, and this guide on how to use it.

Unfortunately, there is no Google Correlate API available for R, so you need to open the website in your browser and upload your weekly outcome data manually. Google gives you 100 correlated search queries within the country you select (Not all countries are available) and you can download the results as a .csv file. What you need for that is a) A Google Account and b) A spreadsheet with your data in a specific format. Here, we don’t want to leak any information from the test data into the training data- So we will ask for correlated queries only for the trained data set, withholding any patterns seen in the test data set. It’s worth noting that there is a “shift series” button. It shifts your data one week in time, if you think there might be a delay between people using Google and the actual reporting of influenza cases (Only shift your data into the past, the predictors should be collected during the week the cases were reported or BEFORE the cases were reported, not afterwards).

For some reason, Google Correlate won’t provide data for the entire time span. It only gives data points untill 2017-03-12. Therefore, we need to extract only the keywords and download the datapoints from google trends.

# Prepare the outcome data in the right format
g.cor.influenza.training.upload = influenza.de[influenza.de$date<split.at,]  
# Adjusting week format: making Sunday the first day of the week
g.cor.influenza.training.upload$date = g.cor.influenza.training.upload$date-1  
# Saving the file in your default folder
# write.table( g.cor.influenza.training.upload, col.names=FALSE,row.names = FALSE,sep=",",file="/FILL_IN_A_PATH/g.cor.influenza.training.upload.csv")  
### --> Now, go to https://www.google.com/trends/correlate/ 
###     Login, upload the spreadhseet, and download results


Upload data to Google Correlate Google Correlate Results
# We have put the Google Correlate data for this example on Github:
github.url = "https://raw.githubusercontent.com/projectflutrend/pft.2/master/input%20data/correlate-g_cor_influenza_training_upload.csv"
# The first 10 lines are text, so we want to skip this part
g.cor.results = read.csv(skip=10,github.url) 
# extracting names, except 'date' and 'y'
g.cor.keywords = names(g.cor.results)[-c(1,2)] 
# R inserted "." for spaces, we have to undo this:
g.cor.keywords = gsub("\\."," ",g.cor.keywords)
g.cor.keywords[1:20] # Showing the first 20 keywords
 [1] "influenza a"                  "influenza"                   
 [3] "virusgrippe"                  "j11 1"                       
 [5] "grippe fieber"                "grippe verlauf"              
 [7] "influenza schnelltest"        "influenza inkubationszeit"   
 [9] "echte grippe"                 "grippe bei kindern"          
[11] "grippe husten"                "grippe influenza"            
[13] "ist grippe ansteckend"        "influenza grippe"            
[15] "symptome grippe"              "j11 1 g"                     
[17] "grippe wie lange"             "symptome influenza"          
[19] "wie lange dauert eine grippe" "verlauf grippe"              

If you wonder what “J11 1” might be - It’s the ICD-10 code for Influenza. Doctors print it on the letters for sick leaves. It seems as if some patients were curious what the doctor had diagnosed them with. Other terms refer to [5] fever, [10] Flu in Children, [11] influenza coughing or [17] Influenza how long

Wikipedia data

McIver & Brownstein made a strong case for the utility of Wikipedia page view data in influenza prediction models. They showed that “Wikipedia usage accurately estimated the week of peak ILI activity 17% more often than Google Flu Trends data and was often more accurate in its measure of ILI intensity”. Combining Google data with Wikipedia may have further advantges.

We can access Wikipedia page view statistics via it’s API (for data from October 2015 until today) and via Wikishark (for data from January 2008 until December 2016)

# On a side note: On Wikipedoa, pages with similar content are linked with each others across different languages. The [Wikipediatrend package](https://cran.r-project.org/web/packages/wikipediatrend/README.html) offers a convenient way to identify these corresponding pages: For 'Influenza', there are about 80 similar pages. While finding the German word for influenza (it's 'influenza'), is not too impressive, automatically finding Wikipedia disease pages in Arabic and Japanese can be quite useful. Wikipedia refers to languages in [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes).
  
# The 'Influenza' page in other languges
wikipediatrend::wp_linked_pages( page= "Influenza",lang="en")[1:10,2:3]
   lang    title        
1  af      Griep        
2  als     Influenza    
3  ar      إنفلونزا     
4  an      Gripe        
5  roa-rup Aremi        
6  as      ইনফ্লুৱেঞ্জা    
7  ast     Gripe        
8  gn      Mba'asypararã
9  ay      Jurma_usu    
10 az      Qrip         


To download Wikipedia page view data, we have to turn to two sifferent sources of data. Wikipedia has its own API, which allows fast and convenient open access. However, the API is only able to retrieve data for > October 2015. Statistics for prior dates are stored in huge files, showing page views per hour in every language. Fortunately, there is a private project, called wikishark, which we can quickly use to download relevant data per week (There used to be another server with an API: stats.grok.se, but it appears to be down since July 2017). For the two time periods, Wikipedia has used different metrics to count page views and to detmerine individual page visitors. Thus, there might be some discrepancies. It is also important to note that Wikipedia is not static. New pages are created and old pages may be changed or merged, i.e. for some pages, that do exist today, we won’t find any meaningful page view statistics in 2010 - So, expect (and ignore) some errors when downloading the data. For further information about limitations and potential influences on view counts, see the discussion and related pages on Wikipedia.

First, we download page view statistics for ‘influenza’. From this page, we can also extract all the links, which refer to other Wikipedia pages (E.g. there are links to pages about Aspirin, allergy, bronchitis etc.), and we can also get the names of the pages which link to the influenza page (Treatments for influenza link to the influenza page, for example, but most ‘backlinks’ are more loosely associated). If you like, you can also add Wikipedia pages manually.

The functions to identify linked pages and to download the statistics are rather long and messy. We cut it short and recommend just loading the functions from github, without going through the code. If you like, you can view the code there.

At the moment, functions only work for the given time span

cat("Data for",length(wikipedia.input.data) -1, "Wikipedia pages were downloaded. \n")
Data for 408 Wikipedia pages were downloaded. 
head(wikipedia.input.data[,1:4])

If you want to reproduce this example and avoid long downloading times, you can download the data here

Merging

Now that we have all the data we need, we can merge the files into one dataframe.

# Combining outcome, wikipedia, google trends and google correlate
influenza.de$date = ISOweek(influenza.de$date ) 
# Merging by week (avoiding any Monday/Sunday or other day issues)
df.full = merge(influenza.de,google.input.data, by="date")
df.full = merge(df.full,wikipedia.input.data, by="date")
# Setting date back to a date
df.full$date = ISOweek2date(paste(df.full$date,"-1",sep="")) # 
cat("Full data set:",dim(df.full)[1], "Weeks and",dim(df.full)[2]-2,"Predictors")
Full data set: 346 Weeks and 535 Predictors

This data set can also be downloaded


Pre-processing

Before the data can be used to build models, we have to split it into training and test data, deal with missing observations, remove predictors with too little variance, transform skewed predictors, think about multi-colinearity and bring all predictors to a common scale.

Splitting the data

Splitting the data into predictor and outcome, as well as training and testing data sets.

# Remeber: 'split.at' defines the date which separates training and test/evaluation data
split = which(df.full$date<split.at) 
df.train = df.full[split,-c(1,2)] # Predictor training data set
y.train = df.full[split,c(2)] # Outcome for training data set
date.train = df.full[split,c(1)] # Date, not a predictor but useful for plotting
df.test  = df.full[-split,-c(1,2)] # Predictors for testing/evaluation data set
y.test = df.full[-split,c(2)] # Outcome for testing data set
date.test = df.full[-split,c(1)] # date for test data set


Missing data points

Furthermore, we remove predictors that have too many (say, >10%) missing values (see figure below). This method will be applied to the test data set, as well- If we wouldn’t, we could end up with predictors that have too many missing values in the test data set to give a proper prediction. Then, we would need to re-run the whole model again without the missing test-predictor, anyway.

When less than 10% of cases are missing, we are going to impute the missing values for the training and test data set separately, using an exponatially weighted moving average.

# Removing features with >10% NAs
  # in training 
  sum.NA.train = as.numeric(lapply(df.train,function(x){sum(is.na(x))})) 
  sum.NA.train = sum.NA.train > length(df.train[,1]) * 0.1 
  if(sum(sum.NA.train)>0){
  df.train = df.train[-which(sum.NA.train)]
  df.test = df.test[which(colnames(df.test) %in% colnames(df.train))]}
  # and test data separately
  sum.NA.test = as.numeric(lapply(df.test,function(x){sum(is.na(x))}))
  sum.NA.test = sum.NA.test > length(df.test[,1]) * 0.1 
  if(sum(sum.NA.test)>0){
  df.test = df.test[-which(sum.NA.test)]
  df.train = df.train[which(colnames(df.train) %in% colnames(df.test))]}
  
# Imputing remaining NAs
  df.train = na.ma(df.train , k = 3, weighting = "exponential") 
  df.test = na.ma(df.test , k = 3, weighting = "exponential") 


Low variance predictors

Furthermore, we want to remove predictors that have near zero variance. This means they have few unique values. Take for example the Wikipedia page on “Samuel Warren Abott”: The page was created only in 2015, so in the time before, the page had mostly 0 page views (Only rarely someone tries to access a Wikipedia pages that are non-existent). Prediction models can be adversely affected by these near zero variance predictors. You might also consider removing predictors that have a very low activity, because results could be very unstable if models put much weight on them (a random increase in interest could distort our model).

# Example: near zero variance 
nearzero.sample = nearZeroVar(df.train,freqCut = 95/5 , uniqueCut = 33)[11] 
ggplot(df.train,aes(x=date.train ,y=df.train[,nearzero.sample])) +
  geom_line() +
  ylab("activity") +
  ggtitle(paste("Near zero variance: ",names(df.train)[nearzero.sample]))

# Removing features with near zero variance
  # identify near zero-variance predictors [only in df.train!]
  nearZeroVar = nearZeroVar(df.train,freqCut = 95/5 , uniqueCut = 25) 
  if(sum(nearZeroVar)>0){
  df.train = df.train[,-nearZeroVar] 
  df.test = df.test[which(colnames(df.test) %in% colnames(df.train))]}

*It is not clear what is going on with the article apout ’Gebr._Klingenberg’ (a German company). Between 2010 and the end of 2015, it was visited only during four weeks (2,1,1,2 page views). Thus, it shows very little variance with regard to the total time span. After October 2015, with the introduction of the new pageview API, we see a drastical change and much more variation. It is not clear what caused this strange behaviour*

Center - Scaling - Skewness reduction

Predictors should be scaled (each value of a predictor is divided by its standard deviation) and centred (All predictor have a mean of zero). This can improve the performance of some of the modelling techniques and makes it easier to compare predictors’ influence. Be aware, though, that the information, about how many absolute visits a Wikipedia page had, will be lost and that can also be more difficult to interpret the results.

Furthermore, many of our predictors are heavily right-skewed. In order to improve the performance of some of our modelling techniques, we will use caret’s ‘BoxCox’ function which automatically determines the appropriate transformation to make the data ‘more normal’.

# Scaling, centering, transofrmation and imputation of remaining NAs by K-nearest neighbours
  preprocess.df.train = preProcess(df.train, method=c("scale","center","BoxCox"))
  df.train = predict(preprocess.df.train, newdata = df.train)
  df.test = predict(preprocess.df.train,newdata = df.test)


Mutli-colinarity

Many of our predictors are strongly correlated with each other - especially the Google Correlate predictor set (see figure below). To takle this issue, you might consider removing some of the predictors, or use principal component analysis (PCA), to reduce the number of predictors by creating a smaller set of uncorrelated components. However, PCA is unsupervised, i.e. it is not guided by the outcome variable when reducing dimensions (in contrast to PLS, see below). Since we did not observe any improvment by using PCA with the present data set, we won’t cover it here.

Most predictors in the Google Correlate Predictor Set are correlated with each other. “Grippe Verlauf” and “Influenza Inkubationszeit” show a correlation of r=.85 for example, and the mean correlation is around r=.8 over the selected 11 predictors

# If you want to use PCA to tackle this issue in R:
 PCA.proces = preProcess(df.raw, method=c("pca"),  # principal component analysis
                          thresh=0.95)             # a cutoff for the cumulative percent of variance                                                    # to be retained by PCA

 df.pca = predict(PCA.proces, newdata = df.raw)



Model building

Overview

We will run several types of models:

Models Resources
partial least squares
ridge regression
lasso regression Tibshirani for a technical or this website and video for a more practical explanation
multivariate adaptive regression splines
support vector machine
single trees
boosted trees
bagged trees
random forest
Cubist
Neural Network

Models will be judged by the accuracy of their predictions root-mean-squared-error (rmse), within the cross-validation set. We will evaluate the performance of the model(s) with the lowest rmse within the test data set.

Time series cross validation

Usual split-sample cross validation is problematic in time series data. A random split of the data, as well as splitting the data based on season-year, would leak information from the future to the past. This is a critical issue in predictors that loose or gain predictive power over time. Consider the following simple example: When we train a model with data from 2010 and 2012 and validate it with data from 2011, information about the predictive power of the keyword ‘influenza 2010’ (which is very high in 2010 but low in 2011) is leaked from 2012 (where it is also low) to 2010.

Therefore, one should use time series cross validation. This means, we will train a model using the data from the first data point until week x and validate the results using data from the next k-weeks. In each validation round, we will move k weeks forward, while the origin stays the same. The amount of data the model is being trained on increases with every round (A model is trained using data from 2010-W30 to 2011-W30 and validated with data from 2011-W31 and W32, in the next round, the model is trained from 2010-W30 to 2011-W32 and validated with data from 2011-W33 and W34, and so on). You could also consider using a moving origin (i.e. the amount of training data stays the same), if you think online behaviour from six years ago is not relevant any more and models should only be based on data from the last two seasons, for example. Small k’s provide more accurate rmse results, but it also increases computational time. In this example we chose a k of 13.

# Setting rolling forward cv with fixed origin
controlObject <- trainControl(method = "timeslice",
                              initialWindow = 52,  # First model is trained on 52 weeks (x)
                              horizon = 52,        # Validation weeks (k)
                              fixedWindow = FALSE, # Origin stays the same
                              allowParallel = TRUE)# Paralel computing can speed things up


Parallel computing

Parallel computing can shorten the time it takes to run these computationally intensive models signficantly.

# parallel computing
no_cores <- detectCores() - 1  
cl <- makeCluster(no_cores, type="FORK")
registerDoParallel(cl)  



Modelling

I have only run a few of models, yet. Takes so much time… Note: Save models on github…

start.time = Sys.time()

# partial least square
  M.pls = train(y= y.train ,
              x = df.train,
              method = "pls",
              tuneLength = 20,
              trControl = controlObject)

# ridge regression (enet)  
  # ridge grid
  ridgeGrid <- expand.grid(.lambda = c(0, .001, .01, .1),.fraction = seq(0.05, 1, length = 10))  
  # model
  M.ridge  = train(y= y.train ,
                 x = df.train,
                 method = "enet",
                 tuneGrid = ridgeGrid,
                 trControl = controlObject)

# lasso regression (glmnet)
  # lasso grid
  lassoGrid <- expand.grid(.alpha = c(0,.2, .4, .6, .8, 1),.lambda = seq(.01, 2, length = 20))
  # Model
  M.lasso <- train(y= y.train ,
                     x = df.train,
                     method = "glmnet",
                    family = "poisson", # gaussian?!
                    tuneGrid = lassoGrid,
                    trControl = controlObject)

# multivariate adaptive regression splines (earth)
  # mars grid
  marsGrid <- expand.grid(.degree = 1, .nprune = 2:25)
  # Model
  M.mars=train(y= y.train ,
          x = df.train,
          method = "earth",
          tuneGrid = marsGrid,
          trControl = controlObject)
  
    # varImp(M.mars)

# radial support vector machine (svmRadial)
  # rsvm grid
  rsvmGrid <-  expand.grid(sigma= 2^c(-25, -20, -15,-10, -5, 0), C= 2^c(0:4))
  # Model
  M.rsvm = train(y= y.train ,
              x = df.train,
              method = "svmRadial",
              tuneLength = 25,
              tuneGrid =rsvmGrid, 
              trControl = controlObject)

# Single regression trees 1 (rpart)
  M.rpart = train(y= y.train ,
                x = df.train,
                method = "rpart",
                tuneLength = 20,
                trControl = controlObject)
  
# Single regression trees 2 (cpart)
  M.ctree = train(y= y.train ,
                x = df.train,
                method = "ctree",
                tuneLength = 20,
                trControl = controlObject)

# Bagged trees (treeBag)
  M.bagtree = train(y= y.train ,
                      x = df.train,
                      method = "treebag",
                      trControl = controlObject)
  
# Random forest (rf)
  M.rf = train(y= y.train , ### takes too long!!!
              x = df.train,
              method = "rf",
              tuneLength = 10,
              ntrees = 500,
              importance = TRUE,
              trControl = controlObject)
  
# Boosted tree (gbm)
  # boostree grid
  boostGrid = expand.grid(.interaction.depth = seq(1, 7, by = 2), .n.trees = seq(100, 1000, by = 50),.shrinkage = c(0.01, 0.1),.n.minobsinnode = c(10))
  # Model
  M.boost = train(y= y.train ,
                  x = df.train,
                  method = "gbm",
                  tuneGrid = boostGrid,
                  verbose = FALSE,
                  trControl = controlObject)
  
# Cubist (cubist)
  # cubist grid
  cubistGrid <- expand.grid(.committees = c(1, 25, 50, 75, 100,125,150,175,200),.neighbors=c(0,1,5,9))
  # Model
  M.cubist = train(y= y.train ,
                   x = df.train,
                   method = "cubist",
                   tuneGrid = cubistGrid,
                   trControl = controlObject)
  
# neural network (avNNet)
  # nnet grid
  nnetGrid <- expand.grid(.decay = c(0.001, .01, .1),.size = seq(1, 27, by = 2),.bag = FALSE)
  # model
  M.nnet = train(y= y.train ,
                x = df.train,
                method = "avNNet",
                tuneGrid = nnetGrid,
                preProcess = "pca", # nnet are often affected by multicolinearity
                linout = TRUE,
                trace = FALSE,
                maxit = 500,
                trControl = controlObject)
  
  finished.time = Sys.time() - start.time
  cat(finished.time)
for(i in 1:length(models.de$result.list)){
  tryCatch({
  models.de$eval.list[[i]] = pft_eval_model(models.de$result.list[[i]])
    names(models.de$eval.list)[i] = names(models.de$result.list)[i]},
  error = function(e){cat(t ,": Error \n")})
}
4 : Error 




Results

Comparing model results

Evaluating the models individually

How you should read the figure below:

Each row represents one type of model. On the left side, you see the results from the model tuning/cross validation. The rmse refers to the mean error within the validation set. The tuning parameters with the lowest rmse are selected for the final model. On the right side, you see the comparison between predicted and observerd values, of the final model, within the complete training data set and the test data set. We expect to see a lower error within the training data set (right plot, orange), than in the test data set (right plot, red) or the validation set (left side). Furthermore, the mean rmse within the validation sets should be comparable to the rmse within the test data set. The most important

NOT INCLUDED, YET:

                         plot(models.de$result.list[[4]] ),
                         models.de$eval.list[[4]]$plots$pred.plot,
                         plot(models.de$result.list[[5]] ),
                         models.de$eval.list[[5]]$plots$pred.plot,
                         plot(models.de$result.list[[6]] ),
                         models.de$eval.list[[6]]$plots$pred.plot,
                         plot(models.de$result.list[[7]] ),
                         models.de$eval.list[[7]]$plots$pred.plot,

Model selection

Nowcast model versu Forecast model

Influenza-Nowcasting


Discussion

Change point analysis

more data sources: twitter, etc etc

transfer the approach to other settings and see what the results are. To the best of our knowledge, digital epidemiology has not been tried in most countries around the world. Results might differ considerably, depending on the degree of digitalization, information seeking behaviour, the sources of information, etc etc.

reporting

Text

…render(“input.Rmd”, html_document(code_download=TRUE))

LS0tCm91dHB1dDoKICBodG1sX25vdGVib29rOiAKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICB0b2NfZGVwdGg6IDUKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIHRvY19kZXB0aDogNApoZWFkZXItaW5jbHVkZXM6Ci0gXHVzZXBhY2thZ2VbbGVmdF17bGluZW5vfQotIFxsaW5lbnVtYmVycwotLS0KPGJyPjxicj4KPGNlbnRlcj48Yj48Zm9udCBzaXplPSI2IiBjb2xvcj0iIzAwOTlmZiI+IC0gUFJFUFJJTlQgLSA8L2ZvbnQ+IDwvYj48L2NlbnRlcj4gIAo8Yj4gPGZvbnQgc2l6ZT01PiBIb3cgdG8gYnVpbGQgYSByZWFsLXRpbWUgcHJlZGljdGlvbiBtb2RlbCBmb3IgaW5mbHVlbnphIGluIEdlcm1hbnk8L2ZvbnQ+IDwvYj4gIAoKX19QYXVsIFNjaG5laWRlcl9fLCAqTWFhc3RyaWNodCBVbml2ZXJzaXR5LCBOZXRoZXJsYW5kcyBJbnN0aXR1dGUgb2YgSGVhbHRoIFNlcnZpY2UgUmVzZWFyY2gqICAKX19EYXZpZCBCYXJuZXR0X18sICpNYWFzdHJpY2h0IFVuaXZlcnNpdHkqICAKX19Kb2huIFBhZ2V0X18sICpOZXRoZXJsYW5kcyBJbnN0aXR1dGUgb2YgSGVhbHRoIFNlcnZpY2UgUmVzZWFyY2gqICAKX19QZXRlciBTcHJlZXV3ZW5iZXJnX18sICpOZXRoZXJsYW5kcyBJbnN0aXR1dGUgb2YgSGVhbHRoIFNlcnZpY2UgUmVzZWFyY2gqICAKX19DaHJpc3RlbCB2YW4gR29vbF9fLCAqTWFhc3RyaWNodCBVbml2ZXJzaXR5KiAgCjxicj4KX19Db250YWN0OiBzY2huZWlkZXIucGF1bHBldGVyQGdtYWlsLmNvbSBfXwoKKioqKgojIyMgU3VtbWFyeQpUaGlzIGlzIGEgZnVsbHkgcmVwcm9kdWNhYmxlIFItdHV0b3JpYWwgb24gaG93IHRvIGJ1aWxkIGEgKG5lYXIpIHJlYWwtdGltZSBwcmVkaWN0aW9uIG1vZGVsIGZvciBpbmZsdWVuemEgaW4gR2VybWFueSwgYmFzZWQgb24gc2VhcmNoIHF1ZXJ5IGRhdGEgZnJvbSBHb29nbGUgVHJlbmRzIGFuZCBwYWdlIHZpZXcgc3RhdGlzdGljcyBmcm9tIFdpa2lwZWRpYS4gQWxsIGNvZGUgYW5kIGRhdGEgaXMgb3Blbmx5IGF2YWlsYWJsZS4gSW5zdHJ1Y3Rpb25zIGluY2x1ZGUgZ3VpZGFuY2Ugb24gaG93IHRvIGlkZW50aWZ5IGFuZCBkb3dubG9hZCByZWxldmFudCBkYXRhLCBmb3JtYXQgYW5kIHByZS1wcm9jZXNzIHRoZSByZXRyaWV2ZWQgaW5mb3JtYXRpb24sIGFuZCBidWlsZCBhbmQgZXZhbHVhdGUgcHJlZGljdGlvbiBtb2RlbHMsIHVzaW5nIG11bHRpcGxlIHN0YXRpc3RpY2FsIG1vZGVsbGluZyB0ZWNobmlxdWVzIChhLmsuYS4gbWFjaGluZSBsZWFybmluZykuIEluIGFkZGl0aW9uLCB3ZSB3aWxsIHByb3ZpZGUgYSBzaG9ydCBpbnRyb2R1Y3Rpb24gdG8gdGhlIGVtZXJnaW5nIGZpZWxkIG9mICdEaWdpdGFsIEVwaWRlbWlvbG9neScgYW5kIGRpc2N1c3Mgc29tZSBvZiBpdHMgY3JpdGljYWwgbWV0aG9kb2xvZ2ljYWwgaXNzdWVzLgoKX19UTDtEUjogW0NvZGUgb25seSB2ZXJzaW9uXShBREQgTElOSylfXwoKTWV0aG9kcyBhcmUgaW5zcGlyZWQgYnkgdGhlIFtjYXJldF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2NhcmV0L2NhcmV0LnBkZikgUi1wYWNrYWdlIGFuZCB0aGUgYm9vayBbQXBwbGllZCBQcmVkaWN0aXZlIE1vZGVsbGluZ10oaHR0cDovL2FwcGxpZWRwcmVkaWN0aXZlbW9kZWxpbmcuY29tLyksIGJvdGggYnkgTWF4IEt1aG4uCgo8YnI+Cgo8YnI+CioqKioKCiMjIyBJbnRyb2R1Y3Rpb24KCiMjIyMgRGlnaXRhbCBFcGlkZW1pb2xvZ3kKSW4gdGhlIHBhc3QsIHBlb3BsZSBjb3VsZCBvbmx5IHdlbnQgdG8gc2VlIHRoZWlyIGRvY3RvciB3aGVuIHRoZXkgd2VyZSBzaWNrIGFuZCB3YW50ZWQgdG8gZ2V0IGluZm9ybWF0aW9uLCBndWlkYW5jZSBhbmQgdHJlYXRtZW50LiBNYW55IG5hdGlvbmFsIGRpc2Vhc2Ugc3VydmVpbGxhbmNlIHN5c3RlbXMgYXJlIHRoZXJlZm9yZSBiYXNlZCBvbiBkb2N0b3JzIGFzIHNlbnRpbmVsczogaGVhbHRoIGNhcmUgcHJvdmlkZXJzIGFuZCBtZWRpY2FsIGxhYm9yYXRvcmllcyBhcmUgYmVpbmcgYXNrZWQgdG8gcmVwb3J0IHRoZSBudW1iZXIgb2YgY2FzZXMgZm9yIGEgY2VydGFpbiBkaXNlYXNlIHRvIHRoZSBzdXJ2ZWlsbGFuY2Ugb3JnYW5pemF0aW9uLiBJbiBHZXJtYW55LCBmb3IgZXhhbXBsZSwgbGFib3JhdG9yaWVzIGFyZSByZXF1aXJlZCB0byByZXBvcnQgYW55IHByb29mIG9mIGluZmx1ZW56YSB2aXJ1c2VzIHRvIHRoZSBsb2NhbCBoZWFsdGggYXV0aG9yaXR5LCB3aGljaCBpbiB0dXJuIHJlcG9ydHMgaXQgdG8gdGhlIFJvYmVydCBLb2NoIEluc3RpdHV0ZS4gTm93YWRheXMsIGhvd2V2ZXIsIG1hbnkgcGVvcGxlIGZpcnN0IHR1cm4gdG8gdGhlIGludGVybmV0LiBUaGV5IHNlZWsgaGVhbHRoIGluZm9ybWF0aW9uIGJ5IGdvb2dsaW5nIGZvciB0aGVpciBzeXNtcHRvbXMsIGxvb2sgdXAgZGlzZWFzZXMgb3IgdHJlYXRtZW50cyBvbiBXaWtpcGVkaWEgb3IgY29ubmVjdCB3aXRoIG90aGVycyB0aHJvdWdoIHNvY2lhbCBtZWRpYS4gRm9yIEVwaWRlbWlvbG9naXN0cywgdGhpcyBvZmZlcnMgdGhlIG9wcG9ydHVuaXR5IHRvIHRyYWNrIHRoZXNlIGRpZ2l0YWwgdHJhY2VzIG9mIHBlb3BsZS4gVGhpcyBoYXMgYmVlbiBkZXNjcmliZWQgYXMgWydEaWdpdGFsIEVwaWRlbWlsb2d5J10oaHR0cDovL2pvdXJuYWxzLnBsb3Mub3JnL3Bsb3Njb21wYmlvbC9hcnRpY2xlP2lkPTEwLjEzNzEvam91cm5hbC5wY2JpLjEwMDI2MTYpOiAiRGlnaXRhbCBkYXRhIHNvdXJjZXMsIHdoZW4gaGFybmVzc2VkIGFwcHJvcHJpYXRlbHksIGNhbiBwcm92aWRlIGxvY2FsIGFuZCB0aW1lbHkgaW5mb3JtYXRpb24gYWJvdXQgZGlzZWFzZSBhbmQgaGVhbHRoIGR5bmFtaWNzIGluIHBvcHVsYXRpb25zIGFyb3VuZCB0aGUgd29ybGQiLiAKCiMjIyMgX19NaWxlc3RvbmVzIG9mIGRpZ2l0YWwgZXBpZGVtaW9sb2d5X18KCiAgKiBJbiAyMDA4LCBHb29nbGUgRmx1IFRyZW5kIHdhcyBsYXVuY2hlZCBhbmQgYWNjb21wYW5pZWQgYnkgYSBicm9hZCBtZWRpYSBjb3ZlcmFnZS4gLi4KICAqIEluIDIwMTQsIE1jSXZlciBhbmQgQnJvd25zdGVpbiBjbGFpbWVkIHRoYXQgV2lraXBlZGlhIHBhZ2UgdmlldyBkYXRhIC4uLgogICogVHdpdHRlcgogICogSGVhbHRoIE1hcAogICogSW5mbHVlbnphIG1hbyBuZXRoZXJsYW5kcwoKIyMjIyBXaHkgSW5mbHVlbnphPyAKSW4gcHJpbmNpcGxlLCB0aGUgbWV0aG9kcyBvZiBzdXJ2ZWlsbGFuY2UsIHdlIHVzZSBpbiB0aGlzIGV4YW1wbGUsIGNhbiBiZSBhcHBsaWVkIHRvIGFueSBkaXNlYXNlLiBIb3dldmVyLCBzb21lIG9mIHRoZSBmZWF0dXJlcyBvZiBpbmZsdWVuemEgbWFrZSBpdCBlYXNpZXIgdG8gZGV0ZWN0IHJlbGV2YW50IHByZWRpY3RvcnMgYW5kIG1vbml0b3IgaXRzIGFjdGl2aXR5LiBGaXJzdCwgaW5mbHVlbnphIGhhcyBhIHN0cm9uZyBzZWFzb25hbGl0eSAoaW4gbW9zdCBjb3VudHJpZXMpLiBFdmVyeSB5ZWFyIHRoZXJlIGlzIGEgc3RhcnQgYW5kIGFuIGVuZCwgYW5kIGR1cmluZyB0aGUgc3VtbWVyIG1vbnRocyB0aGVyZSBhcmUgbGl0ZXJhbGx5IG5vIGNhc2VzIG9mIGluZmx1ZW56YS4gVGhpcyBzdHJvbmcgYW5udWFsIHBhdHRlcm4gY2FuIGJlIGRldGVjdGVkIGVhc2lseS4gSW4gZGlzZWFzZXMsIGluIHdoaWNoIHRoZSBpbmNpZGVuY2UgaXMgbW9yZSBzdGFibGUsIHRoZXJlIG1pZ2h0IGJlIG5vdCBlbm91Z2ggdmFyaWF0aW9uIHRvIGRpc3Rpbmd1aXNoIHNpZ25hbCBhbmQgbm9pc2UuIFNlY29uZCwgZHVyaW5nIHRoZSBmbHUgc2Vhc29uLCBiZXR3ZWVuIDUlIGFuZCAyMCUgb2YgdGhlIHBvcHVsYXRpb24gZ2V0IHNpY2suIFRoZSBvbmxpbmUgdHJhY2VzIG9mIHJhcmUgZGlzZWFzZXMgKHNheSwgYWNoYWxhc2lhKSBwcm9iYWJseSBnZXQgbG9zdCBpbiBub2lzZS4gQW5kIGZpbmFsbHksIGluZmx1ZW56YSBoYXMgZGlzdGluY3Qgc3ltcHRvbXMgdGhhdCBtb3N0IHBlb3BsZSB3aWxsIHJlY29nbml6ZSBhcyBmbHUtc3ltcHRvbXMsIHRoaXMgbWVhbnMgdGhlIHN5bXB0b21zIGFyZSAoc29tZXdoYXQpIHNwZWNpZmljIHRvIGluZmx1ZW56YS4gSW4gY29udHJhc3QsIGRpc2Vhc2VzIHdpdGggb25seSBzdWJ0bGUgb3IgdmVyeSBub24tc3BlY2lmaWMgc3ltcHRvbXMgKHNheSwgZmF0aWd1ZSkgYXJlIGNlcnRhaW5seSBtb3JlIHByb2JsZW1hdGljIHRvIHRyYWNrLiAKCk5ldmVydGhlbGVzcywgdGhlcmUgaXMgbm8gcmVhc29uIHRvIGFzc3VtZSB0aGF0IG90aGVyIGRpc2Vhc2VzIGNhbiBub3QgYmUgc3VydmV5ZWQgdXNpbmcgc2ltaWxhciBtZXRob2RzLiBBIGZldyBwYXBlcnMgaGF2ZSBhbHJlYWR5IHNob3duIHByb21pc2luZyByZXN1bHRzIGluIC4uLkVib2xhLCBUVWJlcmN1bG9zaXMuLi4gW0FERCBMSU5LXVtMSU5LXS4gV2Ugd291bGQgaW5kZWVkIGJlIHZlcnkgaW50ZXJlc3RlZCB0byBzZWUgaG93IHByZWRpY3RpdmUgbW9kZWxzIHBlcmZvcm0gaW4gZnVydGhlciBkaXNlYXNlcywgbGlrZSBhbGxlcmdpYyByaGluaXRpcywgZGVwcmVzc2lvbiBvciBldmVuIHN1bmJ1cm5zLiBJZiB5b3UgaGF2ZSBnb29kIGRhdGEgb24gdGhpcyBvciBrbm93IHdoZXJlIHRvIGdldCBpdCwgd2UgaW52aXRlIHlvdSB0byBmb2xsb3cgdGhpcyB0dXRvcmlhbCwgYnVpbGQgeW91ciBvd24gbW9kZWwocykgYW5kIHNoYXJlIHlvdXIgcmVzdWx0cyB3aXRoIHVzIGFuZCB0aGUgcHVibGljLgoKCiMjIyMgX19Jc3N1ZXNfXwoKVGhlIGh1Z2UgYW1vdW50IG9mIGF2YWlsYWJsZSBkYXRhIGFuZCB0aGUgbm92ZWxpdHkgb2YgbWV0aG9kcyBhcmUgYSBjaGFsbGVuZ2UuICAKT3Zlcmx5IG9wdGltaXN0aWMgYmVjYXVzZSBvZiAKCiAgKiBWYXJpZXR5IG9mIG1ldGhvZHMgYW5kIGRhdGEgc291cmNlcy4KICAqIEluY29uc2lzdGVudCByZXBvcnRpbmcgb2YgbWV0aG9kcyBhbmQgcmVzdWx0cywgdGhlIGFpIHNob3VsZCBiZSByZXByb2R1Y2FiaWxpdHkuIEluIHRoZSBhcmVhIG9mIGRpZ2l0YWwgZXBpZGVtaW9sb2d5LCB0aGlzIG1pZ2h0IGltcGx5IG9wZW4gc2hhcmluZyBvZiBjb2RlIGFuZCBkYXRhLSBvdGhlcndpc2UgcmVwcm9kdWNpYWJpbGl0eSBpcyByYXJlbHkgcG9zc2libGUuCiAgKiBUaGUgZmFpbHVyZSB0byBzdHJpY3RseSBzZXBhcmF0ZSB0cmFpbmlnIGFuZCB0ZXN0IGRhdGEgc2V0cwogICAgICogRXNwZWljYWxseSBpbiBzcGxpdC1jcm9zcyB2YWxpZGF0aW9uLCBsZWFraW5nIGluZm8gZnJvbSB0aGUgZnV0dXJlIHRvIHRoZSBwYXN0CiAgKiByZXBvcnRpbmcgb2YgY29ycmVsYXRpb24gY29lZmljaWVudHMsIGFkanVzdGVkIG9yIG5vdCwgb3Igcm1zZSwgYm90aD8hCiAgKiBSZXNlYXJjaCBhcmVhIG1vc3RseSBpbiB0aGUgVVMgKGNpdGUgZ2xvYmFsIHBhcGVyKSwgdW5jbGVhciB3aGV0aGVyIG1ldGhvZHMgYXJlIHJlcHJvZHVjaWJsZSBpbiBjb3VudHJpZXMgaW4gd2hpY2ggZGlnaXRhbGl6YXRpb24gaXMgbm90IHRoYXQgYWR2YW5jZWQuCgpSZXNlYXJjaCB3aXRoaW4gZGlnaXRhbCBlcGkgbXVzdCBhZGhlcmUgdG8gdGhlIHJpZ291ciBhbmQgY2F1dGlvbiBvdGhlciBlcGlkZW1pb2xvZ2ljYWwgcHJvamVjdCBhcmUgYWxzbyBoZWxkIGFjY291bnRhYmxlIHRvLgo8YnI+CgoqKioqCgo8YnI+CgojIyMgVHV0b3JpYWwKCiMjIyMgT3ZlcnZpZXcgCldlIHdpbGwgYnVpbGQgYSBtb2RlbCBmb3IgcHJlZGljdGluZyB0aGUgaW5mbHVlbnphIGluY2lkZW5jZSAobGFiLWNvbmZpcm1lZCBjYXNlcykgaW4gR2VybWFueSBpbiBuZWFyIHJlYWwtdGltZSAoJ05vd2Nhc3RpbmcnKSwgYnkgdXNpbmcgZGF0YSBmcm9tIHZhcmlvdXMgc291cmNlcy4gV2Ugd2lsbCBzdGFydCB3aXRoIGdldHRpbmcgaW5mb3JtYXRpb24gb24gaW5mbHVlbnphIGluY2lkZW5jZSBpbiBHZXJtYW55LCBzaW5jZSB0aGUgYXZhaWxhYmlsaXR5IG9mIHRoZSBvdXRjb21lIGRhdGEgZGV0ZXJtaW5lcyB0aGUgc2NvcGUgYW5kIHRpbWUgaG9yaXpvbiBvZiB0aGUgbW9kZWwgd2UgYnVpbGQuIFdlIHRoZW4gZG93bmxvYWQgZGF0YSBvbiBzZWFyY2ggcXVlcmllcyBhbmQgcGFnZSB2aWV3IHN0YXRpc3RpY3MgZnJvbSBHb29nbGUgQ29ycmVsYXRlLCBHb29nbGUgVHJlbmRzIGFuZCBXaWtpcGVkaWEsIHJlc3BlY3RpdmVseS4gVGhlIGRhdGEgaGF2ZSB0byBiZSBmb3JtYXR0ZWQsIG1lcmdlZCBhbmQgcHJlLXByb2Nlc3NlZCwgYmVmb3JlIGVudGVyaW5nIGludG8gdGhlIG1vZGVsLiBTdWJzZXF1ZW50bHksIHdlIHdpbGwgdXNlIHZhcmlvdXMgc28gY2FsbGVkIG1hY2hpbmUgbGVhcm5pbmcgbWV0aG9kcyB0byBzZWxlY3QgcmVsZXZhbnQgcHJlZGljdG9ycyBhbmQgYnVpbGQgcHJlZGljdGlvbiBtb2RlbHMuIEZpbmFsbHksIHdlIHdpbGwgY29tcGFyZSB0aGUgZGlmZmVyZW50IG1vZGVscyBpbiB0ZXJtcyBvZiBwcmVkaWN0aXZlIGFjY3VyYWN5IGFuZCBzZWxlY3QgdGhlIGJlc3QgbW9kZWwgdG8gcHJlZGljdCB0aGlzIHdlZWsncyBpbmZsdWVuemEgYWN0aXZpdHkuCgoqV2Ugd291bGQgbGlrZSB0byBwb2ludCBvdXQgdGhhdCB3aGVuIHdlIHNwZWFrIG9mICdwcmVkaWN0aW9ucycsIHdlIHVzZSB0aGUgdGVybSBpbiBpdHMgW3N0YXRpc3RpY2FsIHNlbnNlXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9QcmVkaWN0aW9uI1N0YXRpc3RpY3MpLiBXZSBkbyBub3QgbWVhbiDigJlmb3JlY2FzdGluZywgYXMgaW4gZm9yZWNhc3RpbmcgZnV0dXJlIGluZmx1ZW56YSBjYXNlcy4gV2UgYXJlIGRvaW5nICdOb3djYXN0aW5nJzogV2Ugb2JzZXJ2ZSB3aGF0IHBlb3BsZSB3ZXJlIGRvaW5nIG9uIHRoZSBpbnRlcm5ldCB1bnRpbCB0b2RheSBhbmQgaW5mZXIgZnJvbSB0aGlzIGluZm9ybWF0aW9uIGhvdyBtYW55IGluZmx1ZW56YSBjYXNlcyB0aGVyZSBhcmUgYWJvdXQgbm93Kgo8YnI+CgojIyMjIERhdGEgc291cmNlcwpPdXRvbWUgZGF0YSBpcyB0YWtlbiBmcm9tIHRoZSBbUm9iZXJ0IEtvY2ggSW5zdGl0dXRlXShodHRwczovL3N1cnZzdGF0LnJraS5kZS9Db250ZW50L1F1ZXJ5L1NlbGVjdC5hc3B4KSwgcHJlZGljdG9yIGRhdGEgZnJvbSBbR29vZ2xlIENvcnJlbGF0ZV0oaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS90cmVuZHMvY29ycmVsYXRlLyksIFtHb29nbGUgVHJlbmRzXShodHRwczovL3RyZW5kcy5nb29nbGUuY29tL3RyZW5kcy8pIGFuZCBXaWtpcGVkaWEgdmlhIGl0J3MgW0FQSV0oaHR0cHM6Ly93aWtpdGVjaC53aWtpbWVkaWEub3JnL3dpa2kvQW5hbHl0aWNzL0FRUy9QYWdldmlld3MpIGFuZCBbV2lraXNoYXJrXShodHRwOi8vd3d3Lndpa2lzaGFyay5jb20vKS4gQWxsIGFuYWx5c2VzIGFyZSBwZXJmb3JtZWQgdXNpbmcgdGhlIHN0YXRpc3RpY2FsIHNvZnR3YXJlIFtSL1IgU3R1ZGlvXShodHRwczovL3d3dy5yc3R1ZGlvLmNvbS8pLCBhbmQgcGxlbnR5IG9mIGl0cyBwYWNrYWdlcywgcGFydGljdWxhcmx5IHRoZSBbY2FyZXRdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9jYXJldC9jYXJldC5wZGYpIHBhY2thZ2UuIEV2ZXJ5dGhpbmcgd2UgdXNlIGlzICBhdmFpbGFibGUgb25saW5lIGZvciBmcmVlLiBUaGUgZGF0YSB1c2VkIGluIHRoaXMgZXhhbXBsZSBpcyBhbHNvIGF2YWlsYWJsZSBvbiBnaXRodWIuIElmIHlvdSB3YW50IHRvIGJ1aWxkIHlvdXIgb3duIG1vZGVsLCB5b3UgaGF2ZSB0byBoYXZlIGEgR29vZ2xlIGFjY291bnQgdG8gdXBsb2FkIG91dGNvbWUgZGF0YSB0byBHb29nbGUgQ29ycmVsYXRlLiAKPGJyPgoKIyMjIyBUcmFuc2ZlcmFiaWxpdHkKSWYgeW91IHdhbnQgdG8gdHJhbnNmZXIgdGhpcyBhcHByb2FjaCB0byBhbm90aGVyIHNldHRpbmcgKGFub3RoZXIgY291bnRyeSBvciBhbm90aGVyIGRpc2Vhc2UpLCB0aGUgb25seSB0aGluZyB5b3UgbmVlZCBpcyBhIHNldCBvZiBvdXRjb21lIGRhdGEsIGkuZS4gZ29vZC1xdWFsaXR5IGRhdGEgb2YgdGhlIGFjdHVhbCB3ZWVrbHkgaW5jaWRlbmNlIG9mIHRoZSBkaXNlYXNlLCBvdmVyIGEgc3VmZmljaWVudCB0aW1lIHNwYW4gKDEgeWVhciArKSwgaW4gYSBzcGVjaWZpYyBjb3VudHJ5LiBUaGUgcmVzdCBzaG91bGQgd29yayAobW9yZSBvciBsZXNzKSBhdXRvbWF0aWNhbGx5LCB1bmxlc3MgeW91IHdpc2ggdG8gYWRkIG1vcmUgZGF0YSBzb3VyY2VzIGFzIHByZWRpY3RvcnMsIHdoaWNoIG1heSByZXF1aXJlIHNvbWUgbW9yZSBlZGl0aW5nLiBEZXBlbmRpbmcgb24gdGhlIGNvdW50cnkgaW4gd2hpY2ggeW91IHdhbnQgdG8gcHJlZGljdCBkaXNlYXNlIGFjdGl2aXR5LCB0aGUgYXZhaWxhYmlsaXR5IGFuZCBxdWFsaXR5IG9mIEdvb2dsZSBhbmQgV2lraXBlZGlhIGRhdGEgbWF5IGRpZmZlciBzdWJzdGFudGlhbGx5LiBGb3IgV2lraXBlZGlhIGRhdGEsIHBhZ2UgdmlldyBzdGF0aXN0aWNzIGNhbiBvbmx5IGJlIGRpc3Rpbmd1aXNoZWQgYnkgbGFuZ3VhZ2UsIGJ1dCBub3QgYnkgY291bnRyeS4gTGFuZ3VhZ2UgY2FuIGJlIGEgZ29vZCBwcm94eSBpbiBzb21lIGNhc2VzIChlLmcuIEphcGFuZXNlLCBLb3JlYW4sIEl0YWxpYW4sIGV0Yy4pLCBidXQgYSBwcmV0dHkgYmFkIG9uZSBpbiBvdGhlcnMgKGUuZy4gRnJlbmNoLCBFbmdsaXNoLCBTcGFuaXNoKS4gWW91IGNhbiBsb29rIHVwIGhvdyBtdWNoIG9mIGEgW2NvdW50cnkncyBXaWtpcGVkaWEgdHJhZmZpYyBpcyBpbiBhIHNwZWNmaWZpYyBsYW5ndWFnZV0oaHR0cHM6Ly9zdGF0cy53aWtpbWVkaWEub3JnL3dpa2ltZWRpYS9zcXVpZHMvU3F1aWRSZXBvcnRQYWdlVmlld3NQZXJDb3VudHJ5QnJlYWtkb3duLmh0bSksIGFuZCBob3cgbXVjaCBbV2lraXBlZGlhIHRyYWZmaWMgd2l0aGluIGEgc3BlY2lmaWMgbGFuZ3VhZ2UgaXMgZnJvbSBhIHNwZWNmaWZpYyBjb3VudHJ5XShodHRwczovL3N0YXRzLndpa2ltZWRpYS5vcmcvd2lraW1lZGlhL3NxdWlkcy9TcXVpZFJlcG9ydFBhZ2VWaWV3c1Blckxhbmd1YWdlQnJlYWtkb3duLmh0bSkuIE1vcmVvdmVyLCB3aXRoIHdlZWtseSBkYXRhLCB5b3Ugc2hvdWxkIGJlIGF3YXJlIHRoYXQgdGhlcmUgYXJlIFtkaXZlcmdlbnQgb3BpbmlvbnNdKGh0dHA6Ly9jaGFydHNiaW4uY29tL3ZpZXcvNDE2NzEpIGFib3V0IHdoYXQgaXMgdGhlIGJlc3Qgc3RhcnRpbmcgZGF5IGZvciBhIHdlZWsgKFNhdHVyZGF5PyBTdW5kYXk/IE1vbmRheT8pLgoKKioqKgoKPGJyPgoKIyMjIyBHZXR0aW5nIHN0YXJ0ZWQKQmVmb3JlIGJ1aWxkaW5nIHRoZSBtb2RlbCwgd2UgbmVlZCB0byBpbnN0YWxsIGFuZCBsb2FkIHRoZSByZXF1aXJlZCBSLXBhY2thZ2VzICh5ZXMsIHRoZXJlIGFyZSBhIGxvdCkuLi4KCmBgYHtyIEluc3RhbGxpbmcgYW5kIGxvYWRpbmcgcGFja2FnZXMsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIEluc3RhbGwgYW5kIGxvYWQgYWxsIHJlcXVpcmVkIHBhY2thZ2VzCiMgVGhpcyBtYXkgdGFrZSBhIHdoaWxlLi4uCnJlcXVpcmVkX3BhY2thZ2VzPC1jKCJrbml0ciIsIlJDdXJsIiwiSVNPd2VlayIsImpzb25saXRlIiwiZ2dwbG90MiIsInByb3BoZXQiLCJkcGx5ciIsImd0cmVuZHNSIiwid2lraXBlZGlhdHJlbmQiLCJwYWdldmlld3MiLCJjYXJldCIsImltcHV0ZVRTIiwiZ3JpZEV4dHJhIiwiY293cGxvdCIsImNvcnJwbG90IiwiZG9QYXJhbGxlbCIsImdsbW5ldCIsICJDdWJpc3QiLCJwbHMiLCJkZXZ0b29scyIsInBsb3RtbyIsInBsb3RyaXgiLCJUZWFjaGluZ0RlbW9zIiwiZWFydGgiLCJrZXJubGFiIiwicnBhcnQiLCJwYXJ0eSIsImdyaWQiLCJ6b28iLCJtdnRub3JtIiwibW9kZWx0b29scyIsInN0YXRzNCIsInN0cnVjY2hhbmdlIiwic2FuZHdpY2giLCJlbGFzdGljbmV0IiwibGFycyIsInJhbmRvbUZvcmVzdCIsImlwcmVkIiwiZTEwNzEiKSAjIAoKcGZ0X3BhY2thZ2VzIDwtIGZ1bmN0aW9uKHBhY2thZ2UpewogICAgZm9yKGkgaW4gMTpsZW5ndGgocGFja2FnZSkpewogICAgICBpZihldmFsKHBhcnNlKHRleHQ9cGFzdGUoInJlcXVpcmUoIixwYWNrYWdlW2ldLCIpIikpKT09MCkgewogICAgICAgIGluc3RhbGwucGFja2FnZXMocGFja2FnZSl9fQogICAgcmV0dXJuIChldmFsKHBhcnNlKHRleHQ9cGFzdGUoInJlcXVpcmUoIixwYWNrYWdlLCIpIikpKSl9CgpwZnRfcGFja2FnZXMocmVxdWlyZWRfcGFja2FnZXMpCgojICBndHJlbmRSIGhhcyB0byBiZSB0aGUgZGV2ZWxvcGVyIHZlcnNpb24uIElmIHlvdSBkb24ndCBhbHJlYWR5IGhhdmUgaXQsIGluc3RhbGwgaXQgdGhyb3VnaGdpdGh1YjoKIyAgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJQTWFzc2ljb3R0ZS9ndHJlbmRzUiIpCiMgIGxpYnJhcnkoZ3RyZW5kc1IpCmBgYAo8YnI+CgouLi5hbmQgd2Ugc2hvdWxkIHNwZWNpZnkgYSBmZXcga2V5IHBhcmFtZXRlcnM6CmBgYHtyfQojIFdoYXQgaXMgdGhlIG91dGNvbWUgb2YgaW50ZXJlc3Q/ICAKdGVybSA9ICJJbmZsdWVuemEiICMgIGxhYm9yYXRvcnktY29uZmlybWVkIGNhc2VzICAgIAoKIyBGb3Igd2hpY2ggY291bnRyeSBkbyB3ZSB3YW50IHRvIGJ1aWxkIHRoZSBtb2RlbD8gCmNvdW50cnlfb2ZfaW50ZXJlc3QgPSAiREUiICMgR2VybWFueSBpbiBJU09fMzE2Ni0yIChTZWU6IGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0lTT18zMTY2LTIpICAKCiMgV2hpY2ggbGFuZ3VhZ2UgaXMgcmVsZXZhbnQ/Cmxhbmd1YWdlX29mX2ludGVyZXN0ID0gImRlIiAjIEdlcm1hbiBpbiBJU09fNjM5LTEgKFNlZTogaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTGlzdF9vZl9JU09fNjM5LTFfY29kZXMpICAKCiMgV2hhdCB0aGUgcmVsZXZhbnQgdGltZSBob3Jpem9uIChpLmUuIHRoZSB0aW1lIHNwYW4gd2UgaGF2ZSBkYXRhIGZvcikKZnJvbSA9IGFzLkRhdGUoIjIwMTAtMDctMzEiKSAjIFN0YXJ0CnRvID0gYXMuRGF0ZSgiMjAxNy0wNy0zMSIpICMgRW5kICAgCgojIEhvdyBkbyB3ZSBzcGxpdCB0aGUgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGE/CnNwbGl0LmF0ID0gYXMuRGF0ZSgiMjAxNi0wOC0wMSIpIAojIC0tPiBUcmFpbmluZyBkYXRhOiAgMjAxMC0wNy0zMSAtIDIwMTYtMDgtMDEKIyAtLT4gVGVzdCBEYXRhOiAgICAgIDIwMTYtMDgtMDIgLSAyMDE3LTA3LTMxCmBgYAo8YnI+CgojIyMgR2V0IGRhdGEKCiMjIyMgT3V0Y29tZSBkYXRhCldlIGNhbiBkb3dubG9hZCBHZXJtYW4gaW5mbHVlbnphIGluY2lkZW5jZSBkYXRhIChjYXNlcyBwZXIgcGVyIDEwLDAwMCkgZnJvbSB0aGUgUm9iZXJ0IEtvY2ggSW5zdGl0dXRlLCBmcm9tIEF1Z3VzdCAyMDEwIHVudGlsIEF1Z3VzdCAyMDE3LiBXZSB1cGxvYWRlZCBhIHNwcmVhZGhzZWV0IHRvIGdpdGh1Yiwgc28gaXQgaXMgZWFzaWVyIHRvIGFjY2Vzcy4gSG93ZXZlciwgeW91IGNhbiBnbyB0byBbU3VydnN0YXRdKGh0dHBzOi8vc3VydnN0YXQucmtpLmRlL0NvbnRlbnQvUXVlcnkvU2VsZWN0LmFzcHgpIGFuZCBjdXN0b21pemUgeW91ciBkYXRhIHF1ZXJ5LiBUaGUgZGF0YSBkbyBub3QgY29tZSBpbiB0aGUgZm9ybWF0IHdlIG5lZWQsIHNvIHdlIGhhdmUgdG8gcmUtYXJyYW5nZSBpdCBhIGJpdC4KYGBge3Igc2hvd2luZyBvdXRjb21lIGRhdGEsIGVjaG89VFJVRSwgbWVzc2FnZT1UUlVFLCB3YXJuaW5nPVRSVUUscmVzdWx0cz0nYXNpcyd9CiMgTG9hZCBkYXRhIGZyb20gZ2l0aHViIHJlcG9zaXRvcnkKaW5mbHVlbnphLmRlID0gcmVhZC5jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9wcm9qZWN0Zmx1dHJlbmQvcGZ0LjIvbWFzdGVyL291dGNvbWUlMjBkYXRhL1JLSS5kYXRhLmRlLmNzdiIpCm5hbWVzKGluZmx1ZW56YS5kZSkgPSBjKCJkYXRlIiwieSIpIApoZWFkKGluZmx1ZW56YS5kZSkKCiMgUmUtZm9ybWF0dGluZyAnZGF0ZScsIGFuZCByZW1vdmluZyBpcnJlbGV2YW50IGRhdGEgcG9pbnRzCmluZmx1ZW56YS5kZSRkYXRlID0gcGFzdGUoc3ViKCJ3IiwiVyIsIGluZmx1ZW56YS5kZSRkYXRlKSwiLTEiLCBzZXA9IiIpCmluZmx1ZW56YS5kZSRkYXRlICA9IElTT3dlZWsyZGF0ZShpbmZsdWVuemEuZGUkZGF0ZSkKaW5mbHVlbnphLmRlID0gaW5mbHVlbnphLmRlW2luZmx1ZW56YS5kZSRkYXRlPmFzLkRhdGUoIjIwMTAtMDctMzEiKSAmIGluZmx1ZW56YS5kZSRkYXRlPD1hcy5EYXRlKCIyMDE3LTA3LTMxIiksXQoKIyBUaGUgUktJIGRhdGEgZG9lcyBub3QgcmVwb3J0IGRhdGEgZHVyaW5nIHN1bW1lciBtb250aHMsIGluIHdoaWNoIHRoZXJlIGFyZSBsaXRlcmFsbHkgbm8gaW5mbHVlbnphIGNhc2VzLiBIb3dldmVyLCB3ZSB3YW50IHRvIGhhdmUgYSBtb2RlbCB0aGF0IHByZWRpY3RzIHRob3NlIHplcm8tbW9udGhzIGFzIHN1Y2gsIGFzIHdlbGwuIFRodXMsIHdlIHdpbGwgZmlsbCB0aGUgZ2FwIGFuZCBzZXQgdGhlIGNhc2VzIGZvciB0aGUgaW5zZXJ0ZWQgd2Vla3MgdG8gemVybwpyZWZlcmVuY2UgPWRhdGEuZnJhbWUoZGF0ZSA9IHNlcShmcm9tPW1pbihpbmZsdWVuemEuZGUkZGF0ZSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvPW1heChhcy5EYXRlKGluZmx1ZW56YS5kZSRkYXRlKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5PTcpKQppbmZsdWVuemEuZGUgPSBtZXJnZShyZWZlcmVuY2UsaW5mbHVlbnphLmRlLGJ5PSJkYXRlIixhbGw9VCkKaW5mbHVlbnphLmRlJHlbaXMubmEoaW5mbHVlbnphLmRlJHkpXSA9IDAKCiMgUGxvdHRpbmcgdGhlIGRhdGEKZ2dwbG90KGluZmx1ZW56YS5kZSxhZXMoeD1kYXRlLHk9eSkpICsgCiAgZ2VvbV9saW5lKCkgKyAKICB0aGVtZV9idygpICsgCiAgZ2VvbV9saW5lKCkgKwogIGFubm90YXRlKCJyZWN0IiwgeG1pbj1zcGxpdC5hdCwgCiAgICAgICAgICAgeG1heD1tYXgoaW5mbHVlbnphLmRlJGRhdGUpKzIsIAogICAgICAgICAgIHltaW49bWluKGluZmx1ZW56YS5kZVssMl0pLTIsIAogICAgICAgICAgIHltYXg9bWF4KGluZmx1ZW56YS5kZSR5KSwgCiAgICAgICAgICAgZmlsbD0ib3JhbmdlIiwKICAgICAgICAgICBhbHBoYT0wLjIpICsKICBhbm5vdGF0ZSgidGV4dCIsIAogICAgICAgICAgIHg9bWVkaWFuKGluZmx1ZW56YS5kZSRkYXRlKSwgCiAgICAgICAgICAgeT0yNSwgCiAgICAgICAgICAgbGFiZWw9IHBhc3RlKCJTcGxpdCBiZXR3ZWVuIHRyYWluaW5nL3Rlc3QgZGF0YToiLHNwbGl0LmF0KSwgCiAgICAgICAgICAgc2l6ZT00LGNvbD0ib3JhbmdlIikgKwogIHlsYWIoIkluY2lkZW5jZSBwZXIgMTAsMDAwIikgKwogIGdndGl0bGUoIkluZmx1ZW56YSBpbiBHZXJtYW55IikKYGBgCgpUaGUgZmlndXJlIHNob3dzIHRoZSBpbmZsdWVuemEgc2Vhc29ucyAyMDEwLzIwMTEgdG8gMjAxNi8yMDE3LCBpbiBHZXJtYW55LiBNb3JlIHNwZWNpZmljYWxseSwgaXQgc2hvd3MgJ2xhYm9yYXRvcnktY29uZmlybWVkIGNhc2VzJy4gSXQgc2hvdWxkIGJlIG5vdGVkIHRoYXQgdGhpcyB0eXBlIG9mIG91dGNvbWUgaXMgcHJvYmFibHkgbm90IGlkZWFsIHRvIHVzZSBhcyBhIGdvbGQgc3RhbmRhcmQgZm9yIHRyYWluaW5nIGEgZGlnaXRhbCBzdXJ2ZWlsbGFuY2Ugc3lzdGVtIHRoYXQgaXMgYmFzZWQgb24gc3ltcHRvbXM6IElmIHBlb3BsZSBmZWVsIGxpa2UgaGF2aW5nIHRoZSBmbHUsIHRoZXkgd2lsbCBsb29rIHVwIGluZmx1ZW56YSBvbiBHb29nbGUgYW5kIFdpa2lwZWRpYS4gRmx1LWxpa2Ugc3ltcHRvbXMgdGhhdCBvY2N1ciBkdXJpbmcgdGhlIHN1bW1lciBtb250aHMgYXJlLCBob3dldmVyLCBub3QgY2F1c2VkIGJ5IHRoZSBpbmZsdWVuemEgdmlydXMtIHRoaXMgaXMgd2h5IHRoZSBSb2JlcnQgS29jaCBJbnN0aXR1dGUgZG9lc24ndCByZXBvcnQgYW55IGRhdGEgZm9yIHRoZXNlIG1vbnRocyBhbmQgd2h5IHdlIGhhdmUgdG8gYXNzdW1lIGEgZmxhdCBsaW5lIGF0IHplcm8uIE91ciBwcmVkaWN0aW9uIG1vZGVsIGhhcyB0byBkZWFsIHdpdGggdGhlIGNoYWxsZW5nZSBvZiBkaXN0aW5ndWlzaGluZyBiZXR3ZWVuIHBlb3BsZSB3aG8gaGF2ZSB0aGUgcmVhbCBpbmZsdWVuemEgYW5kIHBlb3BsZSB3aG8gb25seSBoYXZlIGluZmx1ZW56YS1saWtlIHN5bXB0b21zLiAKCnwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAKfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18CnxBcyBhIGNvbXBhcmlzb246IHBhdHRlcm4gb2YgaW5mbHVlbnphLWxpa2UgaWxsbmVzcyBpbiBHZXJtYW55LCBhcyByZXBvcnRlZCBieSB0aGUgW1JvYmVydCBLb2NoIEluc3RpdHV0ZV0oaHR0cHM6Ly9pbmZsdWVuemEucmtpLmRlL1NhaXNvbmJlcmljaHRlLzIwMTUucGRmKS4gSW5mbHVlbnphLWxpa2UgaWxsbmVzcyBpcyBwcm9iYWJseSBtb3JlIGFwcHJvcHJpYXRlIGFzIGFuIG91dGNvbWUgZm9yIG5vd2Nhc3RpbmcgbW9kZWxzLCBhcyBpdCBpcyBhIGJldHRlciByZWZsZWN0aW9uIG9mIHRoZSBpbmNpZGVuY2Ugb2YgaW5mbHVlbnphLWxpa2Ugc3ltcHRvbXMuIEhvd2V2ZXIsIGZvciBHZXJtYW55LCB0aGlzIGRhdGEgaXMgbm90IHB1YmxpY2x5IGF2YWlsYWJsZS4gIHwhW10oaHR0cHM6Ly9naXRodWIuY29tL3Byb2plY3RmbHV0cmVuZC9wZnQuMi9yYXcvbWFzdGVyL3BpY3MvaWxpJTIwZ2VybWFueS5wbmcpICAgIHwKPGJyPgoKCiMjIyMgVGhlICdudWxsLW1vZGVsJwpCZWZvcmUgd2UgZ28gb24gdG8gYnVpbGQgYSBwcmVkaWN0aW9uIG1vZGVsLCB3ZSBzaG91bGQgcXVpY2tseSBkZWZpbmUgd2hhdCB3ZSB3aWxsIGNvbXBhcmUgb3VyICBtb2RlbCBhZ2FpbnN0LiBJdCB3b3VsZCBub3QgYmUgZmFpciB0byBjb21wYXJlIGEgcHJlZGljdGlvbiBtb2RlbCBhZ2FpbnN0IGEgZmxhdCBsaW5lIGF0IHRoZSBtZWFuLCBvciBhdCB6ZXJvIGNhc2VzLiBUaGlzIHdvdWxkIGJlIGxpa2UgYSBjbGluaWNhbCB0cmlhbCB3aXRoIG5vIGNvbXBhcmF0b3IuIFNvLCBsZXQncyBjb21wYXJlIGFnYWluc3QgcGxhY2VibzogT3VyIG1vZGVsIHNob3VsZCAqYXQgbGVhc3QqIGJlIGJldHRlciB0aGFuIGEgJ25haXYnIGZvcmVjYXN0LCB3aGljaCBvbmx5IGtub3dzIHdoYXQgaGFzIGhhcHBlbmQgaW4gdGhlIHBhc3QsIGFuZCBleHRyYXBvbGF0ZXMgdGhlIHBhdHRlcm4gaW50byB0aGUgZnV0dXJlICgqYSBmdXR1cmUgdGhhdCBpcyB0aGUgcHJlc2VudCB0byB1cyopLiBUbyBidWlsZCB0aGlzIE51bGwtTW9kZWwsIHdlIHdpbGwgdXNlIHRoZSBbcHJvcGhldCBwYWNrYWdlXShodHRwczovL3Jlc2VhcmNoLmZiLmNvbS9wcm9waGV0LWZvcmVjYXN0aW5nLWF0LXNjYWxlLykgIHRoYXQgd2FzIGJ1aWxkIGJ5IHBlb3BsZSBhdCBGYWNlYm9vay4gSXQgaXMgc3VwcG9zZWQgdG8gYXV0b21hdGljYWxseSBkZXRlY3QgY2hhbmdlcyBpbiB0cmVuZHMsIGZpdCBhIHBpZWNld2lzZSBsaW5lYXIgZ3Jvd3RoIGN1cnZlIGFuZCBhZGQgYSB5ZWFybHkgc2Vhc29uYWwgY29tcG9uZW50LCBtb2RlbGVkIHVzaW5nIEZvdXJpZXIgc2VyaWVzLiAKCkZvciB0aGUgcHJlc2VudCBkYXRhLCB3ZSB3aWxsIHNwbGl0IHRoZSBkYXRhIHNldCBpbnRvIHRyYWluaW5nIChzZWFvbnMgMjAxMC8yMDExLTIwMTUvMjAxNikgYW5kIHRlc3QgZGF0YSBzZXRzIChzZWFzb24gMjAxNi8yMDE3KSwgd2UgZml0IGEgZm9yZWNhc3QgbW9kZWwgdXNpbmcgdGhlIHRyYWluaW5nIGRhdGEgYW5kIGFzayBmb3IgYSBmb3JlY2FzdCBmb3IgdGhlIG5leHQgeWVhci4gU3Vic2VxdWVudGx5LCB3ZSBjYW4gY29tcGFyZSB0aGUgcHJlZGljdGlvbnMgd2l0aCB0aGUgYWN0dWFsIGRhdGEgZnJvbSB0aGUgdGVzdCBkYXRhIHNldC4KCmBgYHtyIHByb3BoZXQgcHJldmlldywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBTcGxpdGluZyB0aGUgZGF0YSBmb3IgdGhlIGZvcmVjYXN0IG1vZGVsIGludG8gdHJhaW4gYW5kIHRlc3QgZGF0YSBzZXQKdHJhaW4uaW5mbHVlbnphLmRlID0gaW5mbHVlbnphLmRlW2luZmx1ZW56YS5kZSRkYXRlPHNwbGl0LmF0LF0KdGVzdC5pbmZsdWVuemEuZGUgPSBpbmZsdWVuemEuZGVbaW5mbHVlbnphLmRlJGRhdGU+PXNwbGl0LmF0LF0KCiMgJ1Byb3BoZXRpbmcnCm0gPC0gcHJvcGhldChkZj1kYXRhLmZyYW1lKGRzID0gdHJhaW4uaW5mbHVlbnphLmRlJGRhdGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHk9dHJhaW4uaW5mbHVlbnphLmRlJHkpLAogICAgICAgICAgICAgZ3Jvd3RoID0gImxpbmVhciIsCiAgICAgICAgICAgICB5ZWFybHkuc2Vhc29uYWxpdHkgPSBULAogICAgICAgICAgICAgd2Vla2x5LnNlYXNvbmFsaXR5ID0gRikKZnV0dXJlIDwtIG1ha2VfZnV0dXJlX2RhdGFmcmFtZShtLCBwZXJpb2RzID0gMzY1KQpmb3JlY2FzdCA8LSBwcmVkaWN0KG0sIGZ1dHVyZSkKcCA9IHBsb3QobSwgZm9yZWNhc3QpICsKICBnZW9tX3BvaW50KGRhdGE9dGVzdC5pbmZsdWVuemEuZGUsIGFlcyh4PWRhdGUseT15KSxjb2w9InBpbmsiLGx0eT0iZGFzaGVkIiwgc2l6ZT0yKQpwCmBgYAoKVGhlIHByb3BoZXQtZm9yZWNhc3QgYWxnb3JpdGhtIHRyaWVzIHRvIGZpbmQgYSBwYXR0ZXJuIGluIHRoZSBhdmFpbGFibGUgZGF0YSAoMjAxMC8yMDExLTIwMTUvMjAxNikgYW5kIGV4dHJhcG9sYXRlcyBpdCBpbnRvIHRoZSBmdXR1cmUgKDIwMTYvMjAxNykuICBXZSBjYW4gc2VlIHRoYXQgdGhlIDIwMTYvMjAxNyBpbmZsdWVuemEgc2Vhc29uIHdhcyB1bnVzdWFsbHkgc3Ryb25nLCBjb21wYXJlZCB0byB0aGUgbGFzdCBzaXggc2Vhc29ucy4gVGhlIGZvcmVjYXN0IG1vZGVsIHdvdWxkIGhhdmUgdW5kZXJwcmVkaWN0ZWQgdGhlIG51bWJlciBvZiBjYXNlcy4gRnVydGhlcm1vcmUsIHRoZSBzZWFzb24gc3RhcnRlZCBlYWxpZXIgdGhhbiB1c3VhbC4gU28sIHRoZSBmb3JlY2FzdCBtb2RlbCBwcmVkaWN0ZWQgdGhlIG9uc2V0LCBwZWFrIGFuZCBlbmQgb2YgdGhlIHNlYXNvbiBhIGZldyB3ZWVrcyBlYXJsaWVyIHRoYW4gdGhleSBvY2N1cmVkLiBMYXRlciwgd2UgY2FuIHVzZSB0aGlzIGZvcmVjYXN0IGFuZCBjb21wYXJlIGl0IHRvIHdoYXQgb3VyIE5vd2Nhc3QtbW9kZWwgcHJlZGljdHMuIFRoZXJlIGFyZSBwcm9iYWx5IGJldHRlciBtb2RlbHMgdG8gZm9yZWNhc3QgaW5mbHVlbnphLCBidXQgaXQgaXMgc3RpbGwgYmV0dGVyIHRoYW4gbm90aGluZy4KPGJyPgoKIyMjIyBHb29nbGUgQ29ycmVsYXRlCltHb29nbGUgQ29ycmVsYXRlXShodHRwczovL3d3dy5nb29nbGUuY29tL3RyZW5kcy9jb3JyZWxhdGUvKSBpcyBhIHRvb2wgdGhhdCBjYW4gaWRlbnRpZnkgc2VhcmNoIHF1ZXJpZXMgdGhhdCBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQgd2l0aCBhbnkgdGltZSBzZXJpZXMgZGF0YSB5b3UgdXBsb2FkIChFdmVuIGZvciBbcmFuZG9tbHkgZ2VuZXJhdGVkIG51bWJlcnNdKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tL2dvb2dsZS1jb3JyZWxhdGUtY2VydGFpbmx5LWRvZXMtbm90LWltcGx5LWNhdXNhdGlvbi8pIGl0IHdpbGwgZmluZCBhIGdvb2QgbWF0Y2gpLiBJdCB3YXMgYSBidWlsZGluZyBibG9jayBvZiBbR29vZ2xlIEZsdSBUcmVuZF0oaHR0cHM6Ly9nb29nbGVibG9nLmJsb2dzcG90LmNvLnVrLzIwMTEvMDUvbWluaW5nLXBhdHRlcm5zLWluLXNlYXJjaC1kYXRhLXdpdGguaHRtbCkuIEluIGFkZGl0aW9uLCB5b3UgY2FuIGFsc28gaWRlbnRpZnkgcXVlcmllcyB0aGF0IGFyZSBjb3JyZWxhdGVkIHdpdGggYW5vdGhlciBxdWVyeTogRm9yIGV4YW1wbGUsIGZpbmQgcXVlcmllcyB0aGF0IGFyZSByZWxhdGVkIHRvICJJbmZsdWVuemEiIHF1ZXJpZXMuIFNlZSB0aGlzIFtwYXBlcl0oaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS90cmVuZHMvY29ycmVsYXRlL25uc2VhcmNoLnBkZikgZm9yIG1vcmUgaW5mbyBvbiBob3cgR29vZ2xlIENvcnJlbGF0ZSBhbmQgVHJlbmRzIHdvcmtzLCBhbmQgdGhpcyBbZ3VpZGVdKGh0dHA6Ly9wZW9wbGUuaXNjaG9vbC5iZXJrZWxleS5lZHUvfmhhbC9QYXBlcnMvMjAxNS9wcmltZXIucGRmKSBvbiBob3cgdG8gdXNlIGl0LgoKVW5mb3J0dW5hdGVseSwgdGhlcmUgaXMgbm8gR29vZ2xlIENvcnJlbGF0ZSBBUEkgYXZhaWxhYmxlIGZvciBSLCBzbyB5b3UgbmVlZCB0byBvcGVuIHRoZSB3ZWJzaXRlIGluIHlvdXIgYnJvd3NlciBhbmQgdXBsb2FkIHlvdXIgd2Vla2x5IG91dGNvbWUgZGF0YSBtYW51YWxseS4gR29vZ2xlIGdpdmVzIHlvdSAxMDAgY29ycmVsYXRlZCBzZWFyY2ggcXVlcmllcyB3aXRoaW4gdGhlIGNvdW50cnkgeW91IHNlbGVjdCAoTm90IGFsbCBjb3VudHJpZXMgYXJlIGF2YWlsYWJsZSkgYW5kIHlvdSBjYW4gZG93bmxvYWQgdGhlIHJlc3VsdHMgYXMgYSAuY3N2IGZpbGUuIFdoYXQgeW91IG5lZWQgZm9yIHRoYXQgaXMgYSkgQSBHb29nbGUgQWNjb3VudCBhbmQgYikgQSBzcHJlYWRzaGVldCB3aXRoIHlvdXIgZGF0YSBpbiBhIHNwZWNpZmljIGZvcm1hdC4gSGVyZSwgd2UgZG9uJ3Qgd2FudCB0byBsZWFrIGFueSBpbmZvcm1hdGlvbiBmcm9tIHRoZSB0ZXN0IGRhdGEgaW50byB0aGUgdHJhaW5pbmcgZGF0YS0gU28gd2Ugd2lsbCBhc2sgZm9yIGNvcnJlbGF0ZWQgcXVlcmllcyBvbmx5IGZvciB0aGUgdHJhaW5lZCBkYXRhIHNldCwgd2l0aGhvbGRpbmcgYW55IHBhdHRlcm5zIHNlZW4gaW4gdGhlIHRlc3QgZGF0YSBzZXQuIEl0J3Mgd29ydGggbm90aW5nIHRoYXQgdGhlcmUgaXMgYSAic2hpZnQgc2VyaWVzIiBidXR0b24uIEl0IHNoaWZ0cyB5b3VyIGRhdGEgb25lIHdlZWsgaW4gdGltZSwgaWYgeW91IHRoaW5rIHRoZXJlIG1pZ2h0IGJlIGEgZGVsYXkgYmV0d2VlbiBwZW9wbGUgdXNpbmcgR29vZ2xlIGFuZCB0aGUgYWN0dWFsIHJlcG9ydGluZyBvZiBpbmZsdWVuemEgY2FzZXMgKE9ubHkgc2hpZnQgeW91ciBkYXRhIGludG8gdGhlIHBhc3QsIHRoZSBwcmVkaWN0b3JzIHNob3VsZCBiZSBjb2xsZWN0ZWQgZHVyaW5nIHRoZSB3ZWVrIHRoZSBjYXNlcyB3ZXJlIHJlcG9ydGVkIG9yIEJFRk9SRSB0aGUgY2FzZXMgd2VyZSByZXBvcnRlZCwgbm90IGFmdGVyd2FyZHMpLiAKCipGb3Igc29tZSByZWFzb24sIEdvb2dsZSBDb3JyZWxhdGUgd29uJ3QgcHJvdmlkZSBkYXRhIGZvciB0aGUgZW50aXJlIHRpbWUgc3Bhbi4gSXQgb25seSBnaXZlcyBkYXRhIHBvaW50cyB1bnRpbGwgMjAxNy0wMy0xMi4gVGhlcmVmb3JlLCB3ZSBuZWVkIHRvIGV4dHJhY3Qgb25seSB0aGUga2V5d29yZHMgYW5kIGRvd25sb2FkIHRoZSBkYXRhcG9pbnRzIGZyb20gZ29vZ2xlIHRyZW5kcy4qCgpgYGB7ciBwcmVwYXJlIG91dGNvbWUgZGF0YSBmb3IgZy5jb3IsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBQcmVwYXJlIHRoZSBvdXRjb21lIGRhdGEgaW4gdGhlIHJpZ2h0IGZvcm1hdApnLmNvci5pbmZsdWVuemEudHJhaW5pbmcudXBsb2FkID0gaW5mbHVlbnphLmRlW2luZmx1ZW56YS5kZSRkYXRlPHNwbGl0LmF0LF0gIAoKIyBBZGp1c3Rpbmcgd2VlayBmb3JtYXQ6IG1ha2luZyBTdW5kYXkgdGhlIGZpcnN0IGRheSBvZiB0aGUgd2VlawpnLmNvci5pbmZsdWVuemEudHJhaW5pbmcudXBsb2FkJGRhdGUgPSBnLmNvci5pbmZsdWVuemEudHJhaW5pbmcudXBsb2FkJGRhdGUtMSAgCgojIFNhdmluZyB0aGUgZmlsZSBpbiB5b3VyIGRlZmF1bHQgZm9sZGVyCiMgd3JpdGUudGFibGUoIGcuY29yLmluZmx1ZW56YS50cmFpbmluZy51cGxvYWQsIGNvbC5uYW1lcz1GQUxTRSxyb3cubmFtZXMgPSBGQUxTRSxzZXA9IiwiLGZpbGU9Ii9GSUxMX0lOX0FfUEFUSC9nLmNvci5pbmZsdWVuemEudHJhaW5pbmcudXBsb2FkLmNzdiIpICAKCiMjIyAtLT4gTm93LCBnbyB0byBodHRwczovL3d3dy5nb29nbGUuY29tL3RyZW5kcy9jb3JyZWxhdGUvIAojIyMgICAgIExvZ2luLCB1cGxvYWQgdGhlIHNwcmVhZGhzZWV0LCBhbmQgZG93bmxvYWQgcmVzdWx0cwpgYGAKPGJyPgoKfFVwbG9hZCBkYXRhIHRvIEdvb2dsZSBDb3JyZWxhdGUgICB8R29vZ2xlIENvcnJlbGF0ZSBSZXN1bHRzCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQp8IVtdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9wcm9qZWN0Zmx1dHJlbmQvcGZ0LjIvbWFzdGVyL3BpY3MvZ29vZ2xlLmNvcnJlbGF0ZS53ZWJzaXRlMS5wbmcpICB8IVtdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9wcm9qZWN0Zmx1dHJlbmQvcGZ0LjIvbWFzdGVyL3BpY3MvZ29vZ2xlLmNvcnJlbGF0ZS53ZWJzaXRlMi5wbmcpfAoKYGBge3IgZXh0cmFjdCBnLmNvci5rZXl3b3Jkc30KIyBXZSBoYXZlIHB1dCB0aGUgR29vZ2xlIENvcnJlbGF0ZSBkYXRhIGZvciB0aGlzIGV4YW1wbGUgb24gR2l0aHViOgpnaXRodWIudXJsID0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9wcm9qZWN0Zmx1dHJlbmQvcGZ0LjIvbWFzdGVyL2lucHV0JTIwZGF0YS9jb3JyZWxhdGUtZ19jb3JfaW5mbHVlbnphX3RyYWluaW5nX3VwbG9hZC5jc3YiCiMgVGhlIGZpcnN0IDEwIGxpbmVzIGFyZSB0ZXh0LCBzbyB3ZSB3YW50IHRvIHNraXAgdGhpcyBwYXJ0CmcuY29yLnJlc3VsdHMgPSByZWFkLmNzdihza2lwPTEwLGdpdGh1Yi51cmwpIAojIGV4dHJhY3RpbmcgbmFtZXMsIGV4Y2VwdCAnZGF0ZScgYW5kICd5JwpnLmNvci5rZXl3b3JkcyA9IG5hbWVzKGcuY29yLnJlc3VsdHMpWy1jKDEsMildIAojIFIgaW5zZXJ0ZWQgIi4iIGZvciBzcGFjZXMsIHdlIGhhdmUgdG8gdW5kbyB0aGlzOgpnLmNvci5rZXl3b3JkcyA9IGdzdWIoIlxcLiIsIiAiLGcuY29yLmtleXdvcmRzKQoKZy5jb3Iua2V5d29yZHNbMToyMF0gIyBTaG93aW5nIHRoZSBmaXJzdCAyMCBrZXl3b3JkcwpgYGAKKklmIHlvdSB3b25kZXIgd2hhdCAiSjExIDEiIG1pZ2h0IGJlIC0gSXQncyB0aGUgSUNELTEwIGNvZGUgZm9yIEluZmx1ZW56YS4gRG9jdG9ycyBwcmludCBpdCBvbiB0aGUgbGV0dGVycyBmb3Igc2ljayBsZWF2ZXMuIEl0IHNlZW1zIGFzIGlmIHNvbWUgcGF0aWVudHMgd2VyZSBjdXJpb3VzIHdoYXQgdGhlIGRvY3RvciBoYWQgZGlhZ25vc2VkIHRoZW0gd2l0aC4gT3RoZXIgdGVybXMgcmVmZXIgdG8gWzVdIGZldmVyLCBbMTBdIEZsdSBpbiBDaGlsZHJlbiwgWzExXSBpbmZsdWVuemEgY291Z2hpbmcgb3IgWzE3XSBJbmZsdWVuemEgaG93IGxvbmcqCjxicj48YnI+CgojIyMjIEdvb2dsZSB0cmVuZHMKW0dvb2dsZSBUcmVuZHNdKGh0dHBzOi8vdHJlbmRzLmdvb2dsZS5jb20vdHJlbmRzLykgaXMgImEgcHVibGljIHdlYiBmYWNpbGl0eSBvZiBHb29nbGUgSW5jLiwgYmFzZWQgb24gR29vZ2xlIFNlYXJjaCwgdGhhdCBzaG93cyBob3cgb2Z0ZW4gYSBwYXJ0aWN1bGFyIHNlYXJjaC10ZXJtIGlzIGVudGVyZWQgcmVsYXRpdmUgdG8gdGhlIHRvdGFsIHNlYXJjaC12b2x1bWUgYWNyb3NzIHZhcmlvdXMgcmVnaW9ucyBvZiB0aGUgd29ybGQsIGFuZCBpbiB2YXJpb3VzIGxhbmd1YWdlcyIgLSAoZnJvbSBbV2lraXBlZGlhXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Hb29nbGVfVHJlbmRzKSkuIEl0IGNhbiBhbHNvIGJlIHVzZWQgdG8gaWRlbnRpZnkgcmVsYXRlZCBzZWFyY2ggcXVlcmllcyAoIlBlb3BsZSB3aG8gc2VhcmNoZWQgZm9yIHRoaXMsIGFsc28gc2VhcmNoZWQgZm9yIHRoaXMiIC0gbm90aWNlIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gR29vZ2xlIFRyZW5kcyBhbmQgQ29ycmVsYXRlISkKCkZpcnN0LCB3ZSBhcmUgZ29pbmcgdG8gZG93bmxvYWQgdGhlIGRhdGEgZm9yIHRoZSAxMDAga2V5d29yZHMgd2UgaGF2ZSBpZGVudGlmaWVkIHVzaW5nIEdvb2dsZSBDb3JyZWxhdGUuIFNlY29uZCwgd2hpbGUgR29vZ2xlIENvcnJlbGF0ZSB3aWxsIG1vc3Qgb2Z0ZW4gcHJvdmlkZSB5b3Ugd2l0aCBnb29kIHByZWRpY3RvcnMsIHRoZXJlIG1pZ2h0IGJlICBhZGRpdGlvbmFsIHZhbHVlIGluIHRha2luZyBpbnRvIGFjY291bnQgcHJlZGljdG9ycyBmcm9tIEdvb2dsZSBUcmVuZHMgYXMgd2VsbCwgZXZlbiBpZiBvbmx5IHRvIGNvbnRyb2wgZm9yIGNvbmZvdW5kaW5nIG9yIG1lZGlhLWdlbmVyYXRlZCBvdmVyLXByZWRpY3Rpb24uIFRodXMsIHdlIHdpbGwgZG93bmxvYWQgc2VhcmNoIHF1ZXJ5IHN0YXRpc3RpY3MgZm9yICdpbmZsdWVuemEnLCBhbmQgZm9yIDE1IHJlbGF0ZWQgcXVlcmllcy4gVGhpcmQsIHdlIHJldHJpdmUgZGF0YSBmb3IgJ2luZmx1ZW56YSctcXVlcmllcyBpbiBHb29nbGUgTmV3cy4gSWYgeW91IGxpa2UsIHlvdSBjYW4gZXhwYW5kIHlvdXIgcmVxdWVzdHMgYW5kIGluY3JlYXNlIHRoZSBudW1iZXJzIG9mIHByZWRpY3RvcnMgYnkgYWxzbyByZXRyaWV2aW5nIHJlbGF0ZWQga2V5d29yZHMgZnJvbSByZWxhdGVkIGtleXdvcmQsIGZlZWQgcmVsYXRlZCBrZXl3b3JkcyBpbnRvIEdvb2dsZSBDb3JyZWxhdGUsIGFkZCBtb3JlIE5ld3MtcXVlcmllcyBldGMuCgpfX0NhdmVfXzogR29vZ2xlIENvcnJlbGF0ZSBvbmx5IGdpdmVzIHlvdSB3ZWVrbHkgZGF0YSBmb3IgdGltZSBzcGFucyA8IDUgeWVhcnMuIEFzIGEgcG9zc2libGUgd29yay1hcm91bmQsIHdlIHNwbGl0IG91ciB0aW1lIHNlcmllcyBpbnRvIHNtYWxsZXIgY2h1bmtzIGFuZCBkb3dubG9hZCB0aGVtIHNlcGFyYXRlbHkuIEJ1dCBiZWNhdXNlIG9mIHRoZSB3YXkgdGhlIGRhdGEgaXMgZ2VuZXJhdGVkLCBvdmVybGFwcGluZyB0aW1lIHNlcmllcyBkbyBub3QgbWF0Y2ggMTAwJSwgZXZlbiBhZnRlciBhZGp1c3RpbmcgdGhlIHNjYWxlLiBBbHNvLCBpdCBpcyBub3QgY2xlYXIgd2hldGhlciByZXN1bHRzIHdpbGwgYmUgcmVwcm9kdWNpYmxlLCBhcyBHb29nbGUgbWlnaHQgc2hvdyBzbGlnaHRseSBkaWZmZXJlbnQgZmlndXJlcyBpbiBuZXcgcXVlcmllcy4KPGJyPgoKYGBge3IgRmluZCByZWxhdGVkIGdvb2dsZSBxdWVyaWVzLCBtZXNzYWdlPVRSVUUsIHdhcm5pbmc9VFJVRX0KIyBJZGVudGlmeWluZyAxNSByZWxhdGVkIHNlYXJjaCBxdWVyaWVzCiAgIyBXZSBoYXZlIGFscmVhZHkgc2V0IHRoZXNlIHBhcmFtZXRlcnM6IAogICMgdGVybSA9ICJpbmZsdWVuemEiOyBjb3VudHJ5X29mX2ludGVyZXN0PSJERSI7IGZyb209IjIwMTAtMDctMzEiOyB0bz0iMjAxNy0wNy0zMSI7IHN0YXR1cz0gMQogIGdvb2dsZV9wcmltZXIgPSBndHJlbmRzKGtleXdvcmQ9dGVybSwKICAgICAgICAgICAgICAgICAgICAgICAgICBnZW89Y291bnRyeV9vZl9pbnRlcmVzdCwKICAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lPXBhc3RlKGZyb20sdG8pLAogICAgICAgICAgICAgICAgICAgICAgICAgIGdwcm9wID0id2ViIikKCiAgZ29vZ2xlX3JlbGF0ZWQgPSBnb29nbGVfcHJpbWVyJHJlbGF0ZWRfcXVlcmllcyR2YWx1ZVtnb29nbGVfcHJpbWVyJHJlbGF0ZWRfcXVlcmllcyRyZWxhdGVkX3F1ZXJpZXM9PSJ0b3AiXQogIGcudHJlbmRzLmtleXdvcmRzID0gYyh0ZXJtLGdvb2dsZV9yZWxhdGVkKQogIHByaW50KGcudHJlbmRzLmtleXdvcmRzKQpgYGAgCgpUaGUgbG9vcHMgdG8gZG93bmxvYWQgdGhlIHNlYXJjaCBxdWVyaWVzIGFyZSBtZXNzeSBhbmQgcmF0aGVyIGxvbmcuIFdlIHdpbGwganVzdCBsb2FkIHRoZW0gZnJvbSBnaXRodWIsIHdoZXJlIHlvdSBjYW4gaGF2ZSBhIGNsb3NlciBbbG9vayBhdCB0aGVtXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcHJvamVjdGZsdXRyZW5kL3BmdC4yL21hc3Rlci9xdWlja2Z1bmN0aW9ucy9wZnRfYXNrX2dvb2dsZSksIGlmIHlvdSB3YW50LiAKCl9fQXQgdGhlIG1vbWVudCwgZnVuY3Rpb25zIG9ubHkgd29yayBmb3IgdGhlIGdpdmVuIHRpbWUgc3Bhbl9fCmBgYHtyIGd0cmVuZHMgZG93bmxvYWQsIG1lc3NhZ2U9VFJVRSwgd2FybmluZz1GQUxTRX0KIyBMb2FkaW5nIGZ1bmN0aW9ucyBmcm9tIGdpdGh1YjoKZ29vZ2xlLmZ1bmN0aW9uIDwtIGdldFVSTCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Byb2plY3RmbHV0cmVuZC9wZnQuMi9tYXN0ZXIvcXVpY2tmdW5jdGlvbnMvcGZ0X2Fza19nb29nbGUiKQpldmFsKHBhcnNlKHRleHQgPSBnb29nbGUuZnVuY3Rpb24pKQoKIyBGdW5jdGlvbnMgbG9hZGVkIGFyZToKICAjIHBmdF9hc2tfZ29vZ2xlKGtleXdvcmQsY291bnRyeV9vZl9pbnRlcmVzdD0iREUiLGZyb209IjIwMTAtMDctMzEiLHRvPSIyMDE3LTA3LTMxIixzdGF0dXM9IDEscHJlZml4PSJnLnRyZW5kcy4iKQogICMgVG8gc3BsaXQgdGhlIHRpbWUgc3BhbiBhbmQgZG93bmxvYWQgYW5kIG1lcmdlIHF1ZXJ5IHN0YXRpc3RpY3MgZnJvbSBHb29nbGUKCiAgIyBSZXNjYWxlLmd0cmVuZHMoZGYudDEsZGYudDIpIAogICMgSW4gb3JkZXIgdG8gcmVzY2FsZSwgd2UgbG9vayBhdCB0aGUgb3ZlcmxhcHBpbmcgdGltZSBzcGFuIGFuZCB0cnkgdG8gZmluZCB0aGUgYmVzdCBtdXRsaXBsaWNhdGl2ZSBzY2FsaW5nIGZhY3RvciwgdXNpbmcgYSBsaW5lYXIgcmVncmVzc2lvbiwgd2l0aG91dCBjb25zdGFudC4gTm90IHN1cmUgaWYgdGhlcmUgYXJlIGJldHRlciB3YXlzIHRvIGRvIHRoaXMKCiMgRG93bmxvYWQgcXVlcnkgc3RhdGlzdGljcyBmb3IKICAjIGEpIGdvb2dsZS5jb3JyZWxhdGUgcXVlcmllczoKICBnLmNvci5pbnB1dCA9ICBwZnRfYXNrX2dvb2dsZShnLmNvci5rZXl3b3Jkcyxjb3VudHJ5X29mX2ludGVyZXN0PSJERSIsZnJvbT0iMjAxMC0wNy0zMSIsdG89IjIwMTctMDctMzEiLHN0YXR1cz0gMCxwcmVmaXg9ImcuY29yLiIpCiAKICMgYikgdHJlbmRzIHF1ZXJpZXM6CiAgZy50cmVuZHMuaW5wdXQgPSAgcGZ0X2Fza19nb29nbGUoZy50cmVuZHMua2V5d29yZHMsY291bnRyeV9vZl9pbnRlcmVzdD0iREUiLGZyb209IjIwMTAtMDctMzEiLHRvPSIyMDE3LTA3LTMxIixzdGF0dXM9IDAscHJlZml4PSJnLnRyZW5kcy4iKQoKICMgYykgbmV3cyBvbiBpbmZsdWVuemEgYXMgYSBwb3RlbnRpYWxseSByZWxldmFudCAobmVnYXRpdmUpIHByZWRpY3RvcgogIGcubmV3cy5pbnB1dCA9ICBwZnRfYXNrX2dvb2dsZSgiaW5mbHVlbnphIixjb3VudHJ5X29mX2ludGVyZXN0PSJERSIsZnJvbT0iMjAxMC0wNy0zMSIsdG89IjIwMTctMDctMzEiLHN0YXR1cz0gMCxwcmVmaXg9ImcubmV3cy4iLGdwcm9wPSJuZXdzIikKCiMgTWVyZ2UgdGhlIHRocmVlIGRhdCBzZXRzCmdvb2dsZS5pbnB1dC5kYXRhID0gbWVyZ2UoZy5jb3IuaW5wdXQsZy50cmVuZHMuaW5wdXQsYnk9ImRhdGUiLGFsbD1UKQpnb29nbGUuaW5wdXQuZGF0YSA9IG1lcmdlKGdvb2dsZS5pbnB1dC5kYXRhLGcubmV3cy5pbnB1dCxieT0iZGF0ZSIsYWxsPVQpCgpkaW0oZ29vZ2xlLmlucHV0LmRhdGEpCmhlYWQoZ29vZ2xlLmlucHV0LmRhdGFbLDE6NF0pCmBgYAoqSWYgeW91IHdhbnQgdG8gcmVwcm9kdWNlIHRoaXMgZXhhbXBsZSBhbmQgYXZvaWQgbG9uZyBkb3dubG9hZGluZyB0aW1lcywgeW91IGNhbiBkb3dubG9hZCB0aGUgZGF0YSBbaGVyZV0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Byb2plY3RmbHV0cmVuZC9wZnQuMi9tYXN0ZXIvaW5wdXQlMjBkYXRhL2dvb2dsZS5pbnB1dC5kYXRhLmRlLmNzdikqCjxicj4KCiMjIyMgV2lraXBlZGlhIGRhdGEKW01jSXZlciAmIEJyb3duc3RlaW5dKGh0dHA6Ly9qb3VybmFscy5wbG9zLm9yZy9wbG9zY29tcGJpb2wvYXJ0aWNsZT9pZD0xMC4xMzcxL2pvdXJuYWwucGNiaS4xMDAzNTgxKSBtYWRlIGEgc3Ryb25nIGNhc2UgZm9yIHRoZSB1dGlsaXR5IG9mIFdpa2lwZWRpYSBwYWdlIHZpZXcgZGF0YSBpbiBpbmZsdWVuemEgcHJlZGljdGlvbiBtb2RlbHMuIFRoZXkgc2hvd2VkIHRoYXQgIldpa2lwZWRpYSB1c2FnZSBhY2N1cmF0ZWx5IGVzdGltYXRlZCB0aGUgd2VlayBvZiBwZWFrIElMSSBhY3Rpdml0eSAxNyUgbW9yZSBvZnRlbiB0aGFuIEdvb2dsZSBGbHUgVHJlbmRzIGRhdGEgYW5kIHdhcyBvZnRlbiBtb3JlIGFjY3VyYXRlIGluIGl0cyBtZWFzdXJlIG9mIElMSSBpbnRlbnNpdHkiLiBDb21iaW5pbmcgR29vZ2xlIGRhdGEgd2l0aCBXaWtpcGVkaWEgbWF5IGhhdmUgZnVydGhlciBhZHZhbnRnZXMuIAoKV2UgY2FuIGFjY2VzcyBXaWtpcGVkaWEgcGFnZSB2aWV3IHN0YXRpc3RpY3MgdmlhIGl0J3MgW0FQSV0oaHR0cHM6Ly93aWtpdGVjaC53aWtpbWVkaWEub3JnL3dpa2kvQW5hbHl0aWNzL0FRUy9QYWdldmlld3MpIChmb3IgZGF0YSBmcm9tIE9jdG9iZXIgMjAxNSB1bnRpbCB0b2RheSkgYW5kIHZpYSBbV2lraXNoYXJrXShodHRwOi8vd3d3Lndpa2lzaGFyay5jb20vKSAoZm9yIGRhdGEgZnJvbSBKYW51YXJ5IDIwMDggdW50aWwgRGVjZW1iZXIgMjAxNikKCgpgYGB7ciB3aWtpIHRyYW5zbGF0ZX0KIyBPbiBhIHNpZGUgbm90ZTogT24gV2lraXBlZG9hLCBwYWdlcyB3aXRoIHNpbWlsYXIgY29udGVudCBhcmUgbGlua2VkIHdpdGggZWFjaCBvdGhlcnMgYWNyb3NzIGRpZmZlcmVudCBsYW5ndWFnZXMuIFRoZSBbV2lraXBlZGlhdHJlbmQgcGFja2FnZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3dpa2lwZWRpYXRyZW5kL1JFQURNRS5odG1sKSBvZmZlcnMgYSBjb252ZW5pZW50IHdheSB0byBpZGVudGlmeSB0aGVzZSBjb3JyZXNwb25kaW5nIHBhZ2VzOiBGb3IgJ0luZmx1ZW56YScsIHRoZXJlIGFyZSBhYm91dCA4MCBzaW1pbGFyIHBhZ2VzLiBXaGlsZSBmaW5kaW5nIHRoZSBHZXJtYW4gd29yZCBmb3IgaW5mbHVlbnphIChpdCdzICdpbmZsdWVuemEnKSwgaXMgbm90IHRvbyBpbXByZXNzaXZlLCBhdXRvbWF0aWNhbGx5IGZpbmRpbmcgV2lraXBlZGlhIGRpc2Vhc2UgcGFnZXMgaW4gQXJhYmljIGFuZCBKYXBhbmVzZSBjYW4gYmUgcXVpdGUgdXNlZnVsLiBXaWtpcGVkaWEgcmVmZXJzIHRvIGxhbmd1YWdlcyBpbiBbSVNPIDYzOS0xIGNvZGVdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0xpc3Rfb2ZfSVNPXzYzOS0xX2NvZGVzKS4KICAKIyBUaGUgJ0luZmx1ZW56YScgcGFnZSBpbiBvdGhlciBsYW5ndWdlcwp3aWtpcGVkaWF0cmVuZDo6d3BfbGlua2VkX3BhZ2VzKCBwYWdlPSAiSW5mbHVlbnphIixsYW5nPSJlbiIpWzE6MTAsMjozXQpgYGAKPGJyPgoKVG8gZG93bmxvYWQgV2lraXBlZGlhIHBhZ2UgdmlldyBkYXRhLCB3ZSBoYXZlIHRvIHR1cm4gdG8gdHdvIHNpZmZlcmVudCBzb3VyY2VzIG9mIGRhdGEuIFdpa2lwZWRpYSBoYXMgaXRzIG93biBbQVBJXShodHRwczovL3dpa2l0ZWNoLndpa2ltZWRpYS5vcmcvd2lraS9BbmFseXRpY3MvQVFTL1BhZ2V2aWV3cyksIHdoaWNoIGFsbG93cyBmYXN0IGFuZCBjb252ZW5pZW50IG9wZW4gYWNjZXNzLiBIb3dldmVyLCB0aGUgQVBJIGlzIG9ubHkgYWJsZSB0byByZXRyaWV2ZSBkYXRhIGZvciA+IE9jdG9iZXIgMjAxNS4gU3RhdGlzdGljcyBmb3IgcHJpb3IgZGF0ZXMgYXJlIHN0b3JlZCBpbiBodWdlIGZpbGVzLCBzaG93aW5nIHBhZ2Ugdmlld3MgcGVyIGhvdXIgaW4gZXZlcnkgbGFuZ3VhZ2UuIEZvcnR1bmF0ZWx5LCB0aGVyZSBpcyBhIHByaXZhdGUgcHJvamVjdCwgY2FsbGVkIFt3aWtpc2hhcmtdKGh0dHA6Ly93d3cud2lraXNoYXJrLmNvbS8pLCB3aGljaCB3ZSBjYW4gcXVpY2tseSB1c2UgdG8gZG93bmxvYWQgcmVsZXZhbnQgZGF0YSBwZXIgd2VlayAoVGhlcmUgdXNlZCB0byBiZSBhbm90aGVyIHNlcnZlciB3aXRoIGFuIEFQSTogc3RhdHMuZ3Jvay5zZSwgYnV0IGl0IGFwcGVhcnMgdG8gYmUgZG93biBzaW5jZSBKdWx5IDIwMTcpLiBGb3IgdGhlIHR3byB0aW1lIHBlcmlvZHMsIFdpa2lwZWRpYSBoYXMgdXNlZCBkaWZmZXJlbnQgbWV0cmljcyB0byBjb3VudCBwYWdlIHZpZXdzIGFuZCB0byBkZXRtZXJpbmUgaW5kaXZpZHVhbCBwYWdlIHZpc2l0b3JzLiBUaHVzLCB0aGVyZSBtaWdodCBiZSBzb21lIGRpc2NyZXBhbmNpZXMuIEl0IGlzIGFsc28gaW1wb3J0YW50IHRvIG5vdGUgdGhhdCBXaWtpcGVkaWEgaXMgbm90IHN0YXRpYy4gTmV3IHBhZ2VzIGFyZSBjcmVhdGVkIGFuZCBvbGQgcGFnZXMgbWF5IGJlIGNoYW5nZWQgb3IgbWVyZ2VkLCBpLmUuIGZvciBzb21lIHBhZ2VzLCB0aGF0IGRvIGV4aXN0IHRvZGF5LCB3ZSB3b24ndCBmaW5kIGFueSBtZWFuaW5nZnVsIHBhZ2UgdmlldyBzdGF0aXN0aWNzIGluIDIwMTAgLSBTbywgZXhwZWN0IChhbmQgaWdub3JlKSBzb21lIGVycm9ycyB3aGVuIGRvd25sb2FkaW5nIHRoZSBkYXRhLiBGb3IgZnVydGhlciBpbmZvcm1hdGlvbiBhYm91dCBsaW1pdGF0aW9ucyBhbmQgcG90ZW50aWFsIGluZmx1ZW5jZXMgb24gdmlldyBjb3VudHMsIHNlZSB0aGUgZGlzY3Vzc2lvbiBhbmQgcmVsYXRlZCBwYWdlcyBvbiBbV2lraXBlZGlhXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9XaWtpcGVkaWE6UGFnZXZpZXdfc3RhdGlzdGljcykuIAoKRmlyc3QsIHdlIGRvd25sb2FkIHBhZ2UgdmlldyBzdGF0aXN0aWNzIGZvciAnaW5mbHVlbnphJy4gRnJvbSB0aGlzIHBhZ2UsIHdlIGNhbiBhbHNvIGV4dHJhY3QgYWxsIHRoZSBsaW5rcywgd2hpY2ggcmVmZXIgdG8gb3RoZXIgV2lraXBlZGlhIHBhZ2VzIChFLmcuIHRoZXJlIGFyZSBsaW5rcyB0byBwYWdlcyBhYm91dCBBc3BpcmluLCBhbGxlcmd5LCBicm9uY2hpdGlzIGV0Yy4pLCBhbmQgd2UgY2FuIGFsc28gZ2V0IHRoZSBuYW1lcyBvZiB0aGUgcGFnZXMgd2hpY2ggbGluayB0byB0aGUgaW5mbHVlbnphIHBhZ2UgKFRyZWF0bWVudHMgZm9yIGluZmx1ZW56YSBsaW5rIHRvIHRoZSBpbmZsdWVuemEgcGFnZSwgZm9yIGV4YW1wbGUsIGJ1dCBtb3N0ICdiYWNrbGlua3MnIGFyZSBtb3JlIGxvb3NlbHkgYXNzb2NpYXRlZCkuIElmIHlvdSBsaWtlLCB5b3UgY2FuIGFsc28gYWRkIFdpa2lwZWRpYSBwYWdlcyBtYW51YWxseS4gCgpUaGUgZnVuY3Rpb25zIHRvIGlkZW50aWZ5IGxpbmtlZCBwYWdlcyBhbmQgdG8gZG93bmxvYWQgdGhlIHN0YXRpc3RpY3MgYXJlIHJhdGhlciBsb25nIGFuZCBtZXNzeS4gV2UgY3V0IGl0IHNob3J0IGFuZCByZWNvbW1lbmQganVzdCBsb2FkaW5nIHRoZSBmdW5jdGlvbnMgZnJvbSBnaXRodWIsIHdpdGhvdXQgZ29pbmcgdGhyb3VnaCB0aGUgY29kZS4gSWYgeW91IGxpa2UsIHlvdSBjYW4gdmlldyB0aGUgY29kZSBbdGhlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9wcm9qZWN0Zmx1dHJlbmQvcGZ0LjIvYmxvYi9tYXN0ZXIvcXVpY2tmdW5jdGlvbnMvcGZ0X2Fza193aWtpcGVkaWEpLgoKX19BdCB0aGUgbW9tZW50LCBmdW5jdGlvbnMgb25seSB3b3JrIGZvciB0aGUgZ2l2ZW4gdGltZSBzcGFuX18KYGBge3IgZ2V0IHdpa2kgZGF0YX0KIyBMb2FkaW5nIGZ1bmN0aW9ucyBmcm9tIGdpdGh1YjoKd2lraS5mdW5jdGlvbnMgPC0gZ2V0VVJMKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcHJvamVjdGZsdXRyZW5kL3BmdC4yL21hc3Rlci9xdWlja2Z1bmN0aW9ucy9wZnRfYXNrX3dpa2lwZWRpYSIpCmV2YWwocGFyc2UodGV4dCA9IHdpa2kuZnVuY3Rpb25zKSkKCiMgR2V0IG5hbWVzIG9mIGxpbmtlZCBwYWdlcwojIHBmdF93aWtpX2xwKHRlcm0gPSAiSW5mbHVlbnphIiwgbGFuZ3VhZ2Vfb2ZfaW50ZXJlc3QgPSAiZGUiLCBiYWNrbGlua2VkID0gMSAsbWFudWFsLnBhZ2VzPWMoIkhhbHNzY2htZXJ6ZW4iLCJIYXVzbWl0dGVsIikpCgojIEdldCBwYWdlIHZpZXMgc3RhdGlzdGljcwojIHBmdF9hc2tfd2lraXBlZGlhKHBhZ2VzID0gd2lraS5wYWdlcyxsYW5ndWFnZV9vZl9pbnRlcmVzdCA9ICAiZGUiLCBmcm9tID0gYXMuRGF0ZSgiMjAxMC0wMS0wMSIpLHRvID0gYXMuRGF0ZSgiMjAxNy0wNy0zMSIpKQoKIyBFeHRyYWN0aW5nIG5hbWVzIG9mIHBvdGVudGlhbGx5IHJlbGV2YW50IFdpa2lwZWRpYSBwYWdlcwojICsgQWRkaW5nIDIgdGVybXMgbWFudWFsbHk6ICJIYWxzc2NobWVyemVuIiAoPSBzb3JlIHRocm9hdCkgYW5kICJIYXVzbWl0dGVsIiAoPSBob21lIHJlbWVkeSkKd2lraS5wYWdlcyA9IHBmdF93aWtpX2xwKHRlcm0gPSAiSW5mbHVlbnphIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICBsYW5ndWFnZV9vZl9pbnRlcmVzdCA9ICJkZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgYmFja2xpbmtlZCA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICBtYW51YWwucGFnZXM9YygiSGFsc3NjaG1lcnplbiIsIkhhdXNtaXR0ZWwiKSkKc3RyKHdpa2kucGFnZXMpCgojIFdlIGhhdmUgaWRlbnRpZmllZCAxKSB0aGUgaW5mbHVlbnphIHBhZ2UsIAojICAgICAgICAgICAgICAgICAgICAyKSAxNDkgcGFnZXMgdGhhdCBhcmUgbGlua3Mgb24gdGhlIGluZmx1ZW56YSBwYWdlIAojICAgICAgICAgICAgICAgICAgICAzKSA0NTEgcGFnZXMgdGhhdCBsaW5rIHRvIHRoZSBpbmZsdWVuemEgcGFnZSAKIyAgICAgICAgICAgICAgICAgICAgNCkgMiBwYWdlcyB3ZXJlIGFkZGVkIG1hbnVhbGx5CiMgICAgICAgICAgICAgICAgICAgICAgICAgICA9IDYwMyBwb3RlbnRpYWxseSByZWxldmFudCBwYWdlcyBpbiB0b3RhbC4KCiMgTm93LCB3ZSBjYW4gZG93bmxvYWQgdGhlIHBhZ2UgdmlldyBzdGF0aXN0aWNzIAojIERlcGVuZGluZyBvbiB0aGUgYW1vdW50IG9mIHJlbGV2YW50IHBhZ2VzLCB0aGlzIG1heSB0YWtlIHNvbWUgdGltZTogRXhwZWN0IGFwcHJveC4gMS41IHNlY29uZHMgcGVyIHBhZ2UKd2lraXBlZGlhLmlucHV0LmRhdGEgPSBwZnRfYXNrX3dpa2lwZWRpYShwYWdlcyA9IHdpa2kucGFnZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFuZ3VhZ2Vfb2ZfaW50ZXJlc3QgPSAgImRlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJvbSA9IGFzLkRhdGUoIjIwMTAtMDEtMDEiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0byA9IGFzLkRhdGUoIjIwMTctMDctMzEiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGF0dXMgPSAwKQoKY2F0KCJEYXRhIGZvciIsbGVuZ3RoKHdpa2lwZWRpYS5pbnB1dC5kYXRhKSAtMSwgIldpa2lwZWRpYSBwYWdlcyB3ZXJlIGRvd25sb2FkZWQuIFxuIikKaGVhZCh3aWtpcGVkaWEuaW5wdXQuZGF0YVssMTo0XSkKYGBgCipJZiB5b3Ugd2FudCB0byByZXByb2R1Y2UgdGhpcyBleGFtcGxlIGFuZCBhdm9pZCBsb25nIGRvd25sb2FkaW5nIHRpbWVzLCB5b3UgY2FuIGRvd25sb2FkIHRoZSBkYXRhIFtoZXJlXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcHJvamVjdGZsdXRyZW5kL3BmdC4yL21hc3Rlci9pbnB1dCUyMGRhdGEvd2lraXBlZGlhLmlucHV0LmRhdGEuZGUuY3N2KSoKPGJyPgoKIyMjIyBNZXJnaW5nCgpOb3cgdGhhdCB3ZSBoYXZlIGFsbCB0aGUgZGF0YSB3ZSBuZWVkLCB3ZSBjYW4gbWVyZ2UgdGhlIGZpbGVzIGludG8gb25lIGRhdGFmcmFtZS4KCmBgYHtyIG1lcmdpbmcgcHJlZGljdG9yc30KIyBDb21iaW5pbmcgb3V0Y29tZSwgd2lraXBlZGlhLCBnb29nbGUgdHJlbmRzIGFuZCBnb29nbGUgY29ycmVsYXRlCmluZmx1ZW56YS5kZSRkYXRlID0gSVNPd2VlayhpbmZsdWVuemEuZGUkZGF0ZSApIAoKIyBNZXJnaW5nIGJ5IHdlZWsgKGF2b2lkaW5nIGFueSBNb25kYXkvU3VuZGF5IG9yIG90aGVyIGRheSBpc3N1ZXMpCmRmLmZ1bGwgPSBtZXJnZShpbmZsdWVuemEuZGUsZ29vZ2xlLmlucHV0LmRhdGEsIGJ5PSJkYXRlIikKZGYuZnVsbCA9IG1lcmdlKGRmLmZ1bGwsd2lraXBlZGlhLmlucHV0LmRhdGEsIGJ5PSJkYXRlIikKCiMgU2V0dGluZyBkYXRlIGJhY2sgdG8gYSBkYXRlCmRmLmZ1bGwkZGF0ZSA9IElTT3dlZWsyZGF0ZShwYXN0ZShkZi5mdWxsJGRhdGUsIi0xIixzZXA9IiIpKSAjIApjYXQoIkZ1bGwgZGF0YSBzZXQ6IixkaW0oZGYuZnVsbClbMV0sICJXZWVrcyBhbmQiLGRpbShkZi5mdWxsKVsyXS0yLCJQcmVkaWN0b3JzIikKYGBgCipUaGlzIFtkYXRhIHNldF0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Byb2plY3RmbHV0cmVuZC9wZnQuMi9tYXN0ZXIvaW5wdXQlMjBkYXRhL2RmLmZ1bGwuZGUuY3N2KSBjYW4gYWxzbyBiZSBkb3dubG9hZGVkKgo8YnI+CgoqKioqCgojIyMgUHJlLXByb2Nlc3NpbmcKCkJlZm9yZSB0aGUgZGF0YSBjYW4gYmUgdXNlZCB0byBidWlsZCBtb2RlbHMsIHdlIGhhdmUgdG8gc3BsaXQgaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhLCBkZWFsIHdpdGggbWlzc2luZyBvYnNlcnZhdGlvbnMsIHJlbW92ZSBwcmVkaWN0b3JzIHdpdGggdG9vIGxpdHRsZSB2YXJpYW5jZSwgdHJhbnNmb3JtIHNrZXdlZCBwcmVkaWN0b3JzLCB0aGluayBhYm91dCBtdWx0aS1jb2xpbmVhcml0eSBhbmQgYnJpbmcgYWxsIHByZWRpY3RvcnMgdG8gYSBjb21tb24gc2NhbGUuCjxicj48YnI+CgojIyMjIFNwbGl0dGluZyB0aGUgZGF0YQpTcGxpdHRpbmcgdGhlIGRhdGEgaW50byBwcmVkaWN0b3IgYW5kIG91dGNvbWUsIGFzIHdlbGwgYXMgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YSBzZXRzLgoKYGBge3Igc3BsaXR0aW5nIHRoZSBkYXRhIHNldH0KIyBSZW1lYmVyOiAnc3BsaXQuYXQnIGRlZmluZXMgdGhlIGRhdGUgd2hpY2ggc2VwYXJhdGVzIHRyYWluaW5nIGFuZCB0ZXN0L2V2YWx1YXRpb24gZGF0YQpzcGxpdCA9IHdoaWNoKGRmLmZ1bGwkZGF0ZTxzcGxpdC5hdCkgCgpkZi50cmFpbiA9IGRmLmZ1bGxbc3BsaXQsLWMoMSwyKV0gIyBQcmVkaWN0b3IgdHJhaW5pbmcgZGF0YSBzZXQKeS50cmFpbiA9IGRmLmZ1bGxbc3BsaXQsYygyKV0gIyBPdXRjb21lIGZvciB0cmFpbmluZyBkYXRhIHNldApkYXRlLnRyYWluID0gZGYuZnVsbFtzcGxpdCxjKDEpXSAjIERhdGUsIG5vdCBhIHByZWRpY3RvciBidXQgdXNlZnVsIGZvciBwbG90dGluZwoKZGYudGVzdCAgPSBkZi5mdWxsWy1zcGxpdCwtYygxLDIpXSAjIFByZWRpY3RvcnMgZm9yIHRlc3RpbmcvZXZhbHVhdGlvbiBkYXRhIHNldAp5LnRlc3QgPSBkZi5mdWxsWy1zcGxpdCxjKDIpXSAjIE91dGNvbWUgZm9yIHRlc3RpbmcgZGF0YSBzZXQKZGF0ZS50ZXN0ID0gZGYuZnVsbFstc3BsaXQsYygxKV0gIyBkYXRlIGZvciB0ZXN0IGRhdGEgc2V0CmBgYAo8YnI+CgojIyMjIE1pc3NpbmcgZGF0YSBwb2ludHMKRnVydGhlcm1vcmUsIHdlIHJlbW92ZSBwcmVkaWN0b3JzIHRoYXQgaGF2ZSB0b28gbWFueSAoc2F5LCA+MTAlKSBtaXNzaW5nIHZhbHVlcyAoc2VlIGZpZ3VyZSBiZWxvdykuIFRoaXMgbWV0aG9kIHdpbGwgYmUgYXBwbGllZCB0byB0aGUgdGVzdCBkYXRhIHNldCwgYXMgd2VsbC0gSWYgd2Ugd291bGRuJ3QsIHdlIGNvdWxkIGVuZCB1cCB3aXRoIHByZWRpY3RvcnMgdGhhdCBoYXZlIHRvbyBtYW55IG1pc3NpbmcgdmFsdWVzIGluIHRoZSB0ZXN0IGRhdGEgc2V0IHRvIGdpdmUgYSBwcm9wZXIgcHJlZGljdGlvbi4gVGhlbiwgd2Ugd291bGQgbmVlZCB0byByZS1ydW4gdGhlIHdob2xlIG1vZGVsIGFnYWluIHdpdGhvdXQgdGhlIG1pc3NpbmcgdGVzdC1wcmVkaWN0b3IsIGFueXdheS4gCgpXaGVuIGxlc3MgdGhhbiAxMCUgb2YgY2FzZXMgYXJlIG1pc3NpbmcsIHdlIGFyZSBnb2luZyB0byBpbXB1dGUgdGhlIG1pc3NpbmcgdmFsdWVzIGZvciB0aGUgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXQgc2VwYXJhdGVseSwgdXNpbmcgYW4gZXhwb25hdGlhbGx5IHdlaWdodGVkIG1vdmluZyBhdmVyYWdlLiAKCmBgYHtyIHByZXByb2Nlc3MgTkFzfQojIFJlbW92aW5nIGZlYXR1cmVzIHdpdGggPjEwJSBOQXMKICAjIGluIHRyYWluaW5nIAogIHN1bS5OQS50cmFpbiA9IGFzLm51bWVyaWMobGFwcGx5KGRmLnRyYWluLGZ1bmN0aW9uKHgpe3N1bShpcy5uYSh4KSl9KSkgCiAgc3VtLk5BLnRyYWluID0gc3VtLk5BLnRyYWluID4gbGVuZ3RoKGRmLnRyYWluWywxXSkgKiAwLjEgCiAgaWYoc3VtKHN1bS5OQS50cmFpbik+MCl7CiAgZGYudHJhaW4gPSBkZi50cmFpblstd2hpY2goc3VtLk5BLnRyYWluKV0KICBkZi50ZXN0ID0gZGYudGVzdFt3aGljaChjb2xuYW1lcyhkZi50ZXN0KSAlaW4lIGNvbG5hbWVzKGRmLnRyYWluKSldfQogICMgYW5kIHRlc3QgZGF0YSBzZXBhcmF0ZWx5CiAgc3VtLk5BLnRlc3QgPSBhcy5udW1lcmljKGxhcHBseShkZi50ZXN0LGZ1bmN0aW9uKHgpe3N1bShpcy5uYSh4KSl9KSkKICBzdW0uTkEudGVzdCA9IHN1bS5OQS50ZXN0ID4gbGVuZ3RoKGRmLnRlc3RbLDFdKSAqIDAuMSAKICBpZihzdW0oc3VtLk5BLnRlc3QpPjApewogIGRmLnRlc3QgPSBkZi50ZXN0Wy13aGljaChzdW0uTkEudGVzdCldCiAgZGYudHJhaW4gPSBkZi50cmFpblt3aGljaChjb2xuYW1lcyhkZi50cmFpbikgJWluJSBjb2xuYW1lcyhkZi50ZXN0KSldfQogIAojIEltcHV0aW5nIHJlbWFpbmluZyBOQXMKICBkZi50cmFpbiA9IG5hLm1hKGRmLnRyYWluICwgayA9IDMsIHdlaWdodGluZyA9ICJleHBvbmVudGlhbCIpIAogIGRmLnRlc3QgPSBuYS5tYShkZi50ZXN0ICwgayA9IDMsIHdlaWdodGluZyA9ICJleHBvbmVudGlhbCIpIApgYGAKPGJyPgoKIyMjIyBMb3cgdmFyaWFuY2UgcHJlZGljdG9ycwpGdXJ0aGVybW9yZSwgd2Ugd2FudCB0byByZW1vdmUgcHJlZGljdG9ycyB0aGF0IGhhdmUgbmVhciB6ZXJvIHZhcmlhbmNlLiBUaGlzIG1lYW5zIHRoZXkgaGF2ZSBmZXcgdW5pcXVlIHZhbHVlcy4gVGFrZSBmb3IgZXhhbXBsZSB0aGUgV2lraXBlZGlhIHBhZ2Ugb24gIlNhbXVlbCBXYXJyZW4gQWJvdHQiOiBUaGUgcGFnZSB3YXMgY3JlYXRlZCBvbmx5IGluIDIwMTUsIHNvIGluIHRoZSB0aW1lIGJlZm9yZSwgdGhlIHBhZ2UgaGFkIG1vc3RseSAwIHBhZ2Ugdmlld3MgKE9ubHkgcmFyZWx5IHNvbWVvbmUgdHJpZXMgdG8gYWNjZXNzIGEgV2lraXBlZGlhIHBhZ2VzIHRoYXQgYXJlIG5vbi1leGlzdGVudCkuIFByZWRpY3Rpb24gbW9kZWxzIGNhbiBiZSBhZHZlcnNlbHkgYWZmZWN0ZWQgYnkgdGhlc2UgbmVhciB6ZXJvIHZhcmlhbmNlIHByZWRpY3RvcnMuIFlvdSBtaWdodCBhbHNvIGNvbnNpZGVyIHJlbW92aW5nIHByZWRpY3RvcnMgdGhhdCBoYXZlIGEgdmVyeSBsb3cgYWN0aXZpdHksIGJlY2F1c2UgcmVzdWx0cyBjb3VsZCBiZSB2ZXJ5IHVuc3RhYmxlIGlmIG1vZGVscyBwdXQgbXVjaCB3ZWlnaHQgb24gdGhlbSAoYSByYW5kb20gaW5jcmVhc2UgaW4gaW50ZXJlc3QgY291bGQgZGlzdG9ydCBvdXIgbW9kZWwpLgoKYGBge3IgcHJlcHJvY2VzcyBuZWFyemVyb3Zhcn0KIyBFeGFtcGxlOiBuZWFyIHplcm8gdmFyaWFuY2UgCm5lYXJ6ZXJvLnNhbXBsZSA9IG5lYXJaZXJvVmFyKGRmLnRyYWluLGZyZXFDdXQgPSA5NS81ICwgdW5pcXVlQ3V0ID0gMzMpWzExXSAKZ2dwbG90KGRmLnRyYWluLGFlcyh4PWRhdGUudHJhaW4gLHk9ZGYudHJhaW5bLG5lYXJ6ZXJvLnNhbXBsZV0pKSArCiAgZ2VvbV9saW5lKCkgKwogIHlsYWIoImFjdGl2aXR5IikgKwogIGdndGl0bGUocGFzdGUoIk5lYXIgemVybyB2YXJpYW5jZTogIixuYW1lcyhkZi50cmFpbilbbmVhcnplcm8uc2FtcGxlXSkpCgojIFJlbW92aW5nIGZlYXR1cmVzIHdpdGggbmVhciB6ZXJvIHZhcmlhbmNlCiAgIyBpZGVudGlmeSBuZWFyIHplcm8tdmFyaWFuY2UgcHJlZGljdG9ycyBbb25seSBpbiBkZi50cmFpbiFdCiAgbmVhclplcm9WYXIgPSBuZWFyWmVyb1ZhcihkZi50cmFpbixmcmVxQ3V0ID0gOTUvNSAsIHVuaXF1ZUN1dCA9IDI1KSAKICBpZihzdW0obmVhclplcm9WYXIpPjApewogIGRmLnRyYWluID0gZGYudHJhaW5bLC1uZWFyWmVyb1Zhcl0gCiAgZGYudGVzdCA9IGRmLnRlc3Rbd2hpY2goY29sbmFtZXMoZGYudGVzdCkgJWluJSBjb2xuYW1lcyhkZi50cmFpbikpXX0KYGBgCipJdCBpcyBub3QgY2xlYXIgd2hhdCBpcyBnb2luZyBvbiB3aXRoIHRoZSBhcnRpY2xlIGFwb3V0ICdHZWJyLl9LbGluZ2VuYmVyZycgKGEgR2VybWFuIGNvbXBhbnkpLiBCZXR3ZWVuIDIwMTAgYW5kIHRoZSBlbmQgb2YgMjAxNSwgaXQgd2FzIHZpc2l0ZWQgb25seSBkdXJpbmcgZm91ciB3ZWVrcyAoMiwxLDEsMiBwYWdlIHZpZXdzKS4gVGh1cywgaXQgc2hvd3MgdmVyeSBsaXR0bGUgdmFyaWFuY2Ugd2l0aCByZWdhcmQgdG8gdGhlIHRvdGFsIHRpbWUgc3Bhbi4gQWZ0ZXIgT2N0b2JlciAyMDE1LCB3aXRoIHRoZSBpbnRyb2R1Y3Rpb24gb2YgdGhlIG5ldyBwYWdldmlldyBBUEksIHdlIHNlZSBhIGRyYXN0aWNhbCBjaGFuZ2UgYW5kIG11Y2ggbW9yZSB2YXJpYXRpb24uIEl0IGlzIG5vdCBjbGVhciB3aGF0IGNhdXNlZCB0aGlzIHN0cmFuZ2UgYmVoYXZpb3VyKgo8YnI+CgojIyMjIENlbnRlciAtIFNjYWxpbmcgLSBTa2V3bmVzcyByZWR1Y3Rpb24KUHJlZGljdG9ycyBzaG91bGQgYmUgW3NjYWxlZF0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvU3RhbmRhcmRfc2NvcmUpIChlYWNoIHZhbHVlIG9mIGEgcHJlZGljdG9yIGlzIGRpdmlkZWQgYnkgaXRzIHN0YW5kYXJkIGRldmlhdGlvbikgYW5kIFtjZW50cmVkXSgpIChBbGwgcHJlZGljdG9yIGhhdmUgYSBtZWFuIG9mIHplcm8pLiBUaGlzIGNhbiBpbXByb3ZlIHRoZSBwZXJmb3JtYW5jZSBvZiBzb21lIG9mIHRoZSBtb2RlbGxpbmcgdGVjaG5pcXVlcyBhbmQgbWFrZXMgaXQgZWFzaWVyIHRvIGNvbXBhcmUgcHJlZGljdG9ycycgaW5mbHVlbmNlLiBCZSBhd2FyZSwgdGhvdWdoLCB0aGF0IHRoZSBpbmZvcm1hdGlvbiwgYWJvdXQgaG93IG1hbnkgYWJzb2x1dGUgdmlzaXRzIGEgV2lraXBlZGlhIHBhZ2UgaGFkLCB3aWxsIGJlIGxvc3QgYW5kIHRoYXQgY2FuIGFsc28gYmUgbW9yZSBkaWZmaWN1bHQgdG8gaW50ZXJwcmV0IHRoZSByZXN1bHRzLgoKRnVydGhlcm1vcmUsIG1hbnkgb2Ygb3VyIHByZWRpY3RvcnMgYXJlIGhlYXZpbHkgcmlnaHQtc2tld2VkLiBJbiBvcmRlciB0byBpbXByb3ZlIHRoZSBwZXJmb3JtYW5jZSBvZiBzb21lIG9mIG91ciBtb2RlbGxpbmcgdGVjaG5pcXVlcywgd2Ugd2lsbCB1c2UgY2FyZXQncyBbJ0JveENveCddKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1Bvd2VyX3RyYW5zZm9ybSNCb3guRTIuODAuOTNDb3hfdHJhbnNmb3JtYXRpb24pIGZ1bmN0aW9uIHdoaWNoIGF1dG9tYXRpY2FsbHkgZGV0ZXJtaW5lcyB0aGUgYXBwcm9wcmlhdGUgdHJhbnNmb3JtYXRpb24gdG8gbWFrZSB0aGUgZGF0YSAnbW9yZSBub3JtYWwnLiAKCmBgYHtyfQojIFNjYWxpbmcsIGNlbnRlcmluZywgdHJhbnNvZnJtYXRpb24gYW5kIGltcHV0YXRpb24gb2YgcmVtYWluaW5nIE5BcyBieSBLLW5lYXJlc3QgbmVpZ2hib3VycwogIHByZXByb2Nlc3MuZGYudHJhaW4gPSBwcmVQcm9jZXNzKGRmLnRyYWluLCBtZXRob2Q9Yygic2NhbGUiLCJjZW50ZXIiLCJCb3hDb3giKSkKICBkZi50cmFpbiA9IHByZWRpY3QocHJlcHJvY2Vzcy5kZi50cmFpbiwgbmV3ZGF0YSA9IGRmLnRyYWluKQogIGRmLnRlc3QgPSBwcmVkaWN0KHByZXByb2Nlc3MuZGYudHJhaW4sbmV3ZGF0YSA9IGRmLnRlc3QpCmBgYAo8YnI+CgojIyMjIE11dGxpLWNvbGluYXJpdHkgCk1hbnkgb2Ygb3VyIHByZWRpY3RvcnMgYXJlIHN0cm9uZ2x5IGNvcnJlbGF0ZWQgd2l0aCBlYWNoIG90aGVyIC0gZXNwZWNpYWxseSB0aGUgR29vZ2xlIENvcnJlbGF0ZSBwcmVkaWN0b3Igc2V0IChzZWUgZmlndXJlIGJlbG93KS4gVG8gdGFrbGUgdGhpcyBpc3N1ZSwgeW91IG1pZ2h0IGNvbnNpZGVyIHJlbW92aW5nIHNvbWUgb2YgdGhlIHByZWRpY3RvcnMsIG9yIHVzZSBbcHJpbmNpcGFsIGNvbXBvbmVudCBhbmFseXNpc10oTGluaykgKFBDQSksIHRvIHJlZHVjZSB0aGUgbnVtYmVyIG9mIHByZWRpY3RvcnMgYnkgY3JlYXRpbmcgYSBzbWFsbGVyIHNldCBvZiB1bmNvcnJlbGF0ZWQgY29tcG9uZW50cy4gSG93ZXZlciwgUENBIGlzIHVuc3VwZXJ2aXNlZCwgaS5lLiBpdCBpcyBub3QgZ3VpZGVkIGJ5IHRoZSBvdXRjb21lIHZhcmlhYmxlIHdoZW4gcmVkdWNpbmcgZGltZW5zaW9ucyAoaW4gY29udHJhc3QgdG8gUExTLCBzZWUgYmVsb3cpLiBTaW5jZSB3ZSBkaWQgbm90IG9ic2VydmUgYW55IGltcHJvdm1lbnQgYnkgdXNpbmcgUENBIHdpdGggdGhlIHByZXNlbnQgZGF0YSBzZXQsIHdlIHdvbid0IGNvdmVyIGl0IGhlcmUuICAKCmBgYHtyIGNvcnJwbG90IGRlbW8sIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CiMgRXhhbXBsZTogQXNzZXNzaW5nIG11bHRpY29saW5lcml0eQojIFNlbGVjdGluZyBhIHN1YnNldCBhbmQgcGxvdHRpbmcgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeApzdWJzZXQuZy5jb3IgPSBnb29nbGUuaW5wdXQuZGF0YVssNjoxNl0gCm5hbWVzKHN1YnNldC5nLmNvcikgPSBnc3ViKCJnLmNvci4iLCIiLG5hbWVzKHN1YnNldC5nLmNvcikpCmNvcnJlbGF0aW9uLm1hdHJpeCA9IGNvcihzdWJzZXQuZy5jb3IpICAKY29ycnBsb3Q6OmNvcnJwbG90KGNvcnJlbGF0aW9uLm1hdHJpeCxtZXRob2Q9InBpZSIsIHRpdGxlPSJDb3JyZWxhdGlvbiBiZXR3ZWVuIEdvb2dsZSBDb3JyZWxhdGUgZmVhdHVyZXMiLHRsLnBvcz0ibHQiLHRsLmNleD0wLjcsIHRsLmNvbCA9ImJsYWNrIiwgdGwuc3J0PTQ1KQpgYGAKKk1vc3QgcHJlZGljdG9ycyBpbiB0aGUgR29vZ2xlIENvcnJlbGF0ZSBQcmVkaWN0b3IgU2V0IGFyZSBjb3JyZWxhdGVkIHdpdGggZWFjaCBvdGhlci4gIkdyaXBwZSBWZXJsYXVmIiBhbmQgIkluZmx1ZW56YSBJbmt1YmF0aW9uc3plaXQiIHNob3cgYSBjb3JyZWxhdGlvbiBvZiByPS44NSBmb3IgZXhhbXBsZSwgYW5kIHRoZSBtZWFuIGNvcnJlbGF0aW9uIGlzIGFyb3VuZCByPS44IG92ZXIgdGhlIHNlbGVjdGVkIDExIHByZWRpY3RvcnMqIAo8YnI+CmBgYAojIElmIHlvdSB3YW50IHRvIHVzZSBQQ0EgdG8gdGFja2xlIHRoaXMgaXNzdWUgaW4gUjoKIFBDQS5wcm9jZXMgPSBwcmVQcm9jZXNzKGRmLnJhdywgbWV0aG9kPWMoInBjYSIpLCAgIyBwcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGhyZXNoPTAuOTUpICAgICAgICAgICAgICMgYSBjdXRvZmYgZm9yIHRoZSBjdW11bGF0aXZlIHBlcmNlbnQgb2YgdmFyaWFuY2UgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyB0byBiZSByZXRhaW5lZCBieSBQQ0EKCiBkZi5wY2EgPSBwcmVkaWN0KFBDQS5wcm9jZXMsIG5ld2RhdGEgPSBkZi5yYXcpCmBgYAo8YnI+CgoqKioqCgojIyMgTW9kZWwgYnVpbGRpbmcKCiMjIyMgT3ZlcnZpZXcgCldlIHdpbGwgcnVuIHNldmVyYWwgdHlwZXMgb2YgbW9kZWxzOgoKfCAgTW9kZWxzICAgICAgICAgICAgICAgICAgIHwgICBSZXNvdXJjZXMgICAgICAgICAgICAgIHwKfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwKfCAgIHBhcnRpYWwgbGVhc3Qgc3F1YXJlcyAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICAgcmlkZ2UgcmVncmVzc2lvbiAgICAgICAgfCB8CnwgICBsYXNzbyByZWdyZXNzaW9uIHxbVGlic2hpcmFuaV0oaHR0cDovL3N0YXR3ZWIuc3RhbmZvcmQuZWR1L350aWJzL2xhc3NvL2xhc3NvLnBkZikgZm9yIGEgdGVjaG5pY2FsIG9yIHRoaXMgW3dlYnNpdGUgYW5kIHZpZGVvXShodHRwczovL3d3dy5yLWJsb2dnZXJzLmNvbS90cmV2b3ItaGFzdGllLXByZXNlbnRzLWdsbW5ldC1sYXNzby1hbmQtZWxhc3RpYy1uZXQtcmVndWxhcml6YXRpb24taW4tci8pIGZvciBhIG1vcmUgcHJhY3RpY2FsIGV4cGxhbmF0aW9uIHwKfCAgIG11bHRpdmFyaWF0ZSBhZGFwdGl2ZSByZWdyZXNzaW9uIHNwbGluZXMgIHwgfAp8ICAgc3VwcG9ydCB2ZWN0b3IgbWFjaGluZXwgfAp8ICAgc2luZ2xlIHRyZWVzICB8IHwKfCAgIGJvb3N0ZWQgdHJlZXMgIHwgfAp8ICAgYmFnZ2VkIHRyZWVzICB8IHwKfCAgIHJhbmRvbSBmb3Jlc3QgIHwgfAp8ICAgQ3ViaXN0ICB8ICB8CnwgICBOZXVyYWwgTmV0d29yayAgfCB8CiAgCk1vZGVscyB3aWxsIGJlIGp1ZGdlZCBieSB0aGUgYWNjdXJhY3kgb2YgdGhlaXIgcHJlZGljdGlvbnMgcm9vdC1tZWFuLXNxdWFyZWQtZXJyb3IgKHJtc2UpLCB3aXRoaW4gdGhlIGNyb3NzLXZhbGlkYXRpb24gc2V0LiBXZSB3aWxsIGV2YWx1YXRlIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgbW9kZWwocykgd2l0aCB0aGUgbG93ZXN0IHJtc2Ugd2l0aGluIHRoZSB0ZXN0IGRhdGEgc2V0Lgo8YnI+PGJyPgoKIyMjIyBUaW1lIHNlcmllcyBjcm9zcyB2YWxpZGF0aW9uClVzdWFsIHNwbGl0LXNhbXBsZSBjcm9zcyB2YWxpZGF0aW9uIGlzIHByb2JsZW1hdGljIGluIHRpbWUgc2VyaWVzIGRhdGEuIEEgcmFuZG9tIHNwbGl0IG9mIHRoZSBkYXRhLCBhcyB3ZWxsIGFzIHNwbGl0dGluZyB0aGUgZGF0YSBiYXNlZCBvbiBzZWFzb24teWVhciwgd291bGQgbGVhayBpbmZvcm1hdGlvbiBmcm9tIHRoZSBmdXR1cmUgdG8gdGhlIHBhc3QuIFRoaXMgaXMgYSBjcml0aWNhbCBpc3N1ZSBpbiBwcmVkaWN0b3JzIHRoYXQgbG9vc2Ugb3IgZ2FpbiBwcmVkaWN0aXZlIHBvd2VyIG92ZXIgdGltZS4gQ29uc2lkZXIgdGhlIGZvbGxvd2luZyBzaW1wbGUgZXhhbXBsZTogV2hlbiB3ZSB0cmFpbiBhIG1vZGVsIHdpdGggZGF0YSBmcm9tIDIwMTAgYW5kIDIwMTIgYW5kIHZhbGlkYXRlIGl0IHdpdGggZGF0YSBmcm9tIDIwMTEsIGluZm9ybWF0aW9uIGFib3V0IHRoZSBwcmVkaWN0aXZlIHBvd2VyIG9mIHRoZSBrZXl3b3JkICdpbmZsdWVuemEgMjAxMCcgKHdoaWNoIGlzIHZlcnkgaGlnaCBpbiAyMDEwIGJ1dCBsb3cgaW4gMjAxMSkgaXMgbGVha2VkIGZyb20gMjAxMiAod2hlcmUgaXQgaXMgYWxzbyBsb3cpIHRvIDIwMTAuIAoKVGhlcmVmb3JlLCBvbmUgc2hvdWxkIHVzZSB0aW1lIHNlcmllcyBjcm9zcyB2YWxpZGF0aW9uLiBUaGlzIG1lYW5zLCB3ZSB3aWxsIHRyYWluIGEgbW9kZWwgdXNpbmcgdGhlIGRhdGEgZnJvbSB0aGUgZmlyc3QgZGF0YSBwb2ludCB1bnRpbCB3ZWVrIHggYW5kIHZhbGlkYXRlIHRoZSByZXN1bHRzIHVzaW5nIGRhdGEgZnJvbSB0aGUgbmV4dCBrLXdlZWtzLiBJbiBlYWNoIHZhbGlkYXRpb24gcm91bmQsIHdlIHdpbGwgbW92ZSBrIHdlZWtzIGZvcndhcmQsIHdoaWxlIHRoZSBvcmlnaW4gc3RheXMgdGhlIHNhbWUuIFRoZSBhbW91bnQgb2YgZGF0YSB0aGUgbW9kZWwgaXMgYmVpbmcgdHJhaW5lZCBvbiBpbmNyZWFzZXMgd2l0aCBldmVyeSByb3VuZCAoQSBtb2RlbCBpcyB0cmFpbmVkIHVzaW5nIGRhdGEgZnJvbSAyMDEwLVczMCB0byAyMDExLVczMCBhbmQgdmFsaWRhdGVkIHdpdGggZGF0YSBmcm9tIDIwMTEtVzMxIGFuZCBXMzIsIGluIHRoZSBuZXh0IHJvdW5kLCB0aGUgbW9kZWwgaXMgdHJhaW5lZCBmcm9tIDIwMTAtVzMwIHRvIDIwMTEtVzMyIGFuZCB2YWxpZGF0ZWQgd2l0aCBkYXRhIGZyb20gMjAxMS1XMzMgYW5kIFczNCwgYW5kIHNvIG9uKS4gWW91IGNvdWxkIGFsc28gY29uc2lkZXIgdXNpbmcgYSBtb3Zpbmcgb3JpZ2luIChpLmUuIHRoZSBhbW91bnQgb2YgdHJhaW5pbmcgZGF0YSBzdGF5cyB0aGUgc2FtZSksIGlmIHlvdSB0aGluayBvbmxpbmUgYmVoYXZpb3VyIGZyb20gc2l4IHllYXJzIGFnbyBpcyBub3QgcmVsZXZhbnQgYW55IG1vcmUgYW5kIG1vZGVscyBzaG91bGQgb25seSBiZSBiYXNlZCBvbiBkYXRhIGZyb20gdGhlIGxhc3QgdHdvIHNlYXNvbnMsIGZvciBleGFtcGxlLiBTbWFsbCBrJ3MgcHJvdmlkZSBtb3JlIGFjY3VyYXRlIHJtc2UgcmVzdWx0cywgYnV0IGl0IGFsc28gaW5jcmVhc2VzIGNvbXB1dGF0aW9uYWwgdGltZS4gSW4gdGhpcyBleGFtcGxlIHdlIGNob3NlIGEgayBvZiAxMy4KICAKYGBge3J9CiMgU2V0dGluZyByb2xsaW5nIGZvcndhcmQgY3Ygd2l0aCBmaXhlZCBvcmlnaW4KY29udHJvbE9iamVjdCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gInRpbWVzbGljZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluaXRpYWxXaW5kb3cgPSA1MiwgICMgRmlyc3QgbW9kZWwgaXMgdHJhaW5lZCBvbiA1MiB3ZWVrcyAoeCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG9yaXpvbiA9IDUyLCAgICAgICAgIyBWYWxpZGF0aW9uIHdlZWtzIChrKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXhlZFdpbmRvdyA9IEZBTFNFLCAjIE9yaWdpbiBzdGF5cyB0aGUgc2FtZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGxvd1BhcmFsbGVsID0gVFJVRSkjIFBhcmFsZWwgY29tcHV0aW5nIGNhbiBzcGVlZCB0aGluZ3MgdXAKYGBgCjxicj4KCiMjIyMgUGFyYWxsZWwgY29tcHV0aW5nClBhcmFsbGVsIGNvbXB1dGluZyBjYW4gc2hvcnRlbiB0aGUgdGltZSBpdCB0YWtlcyB0byBydW4gdGhlc2UgY29tcHV0YXRpb25hbGx5IGludGVuc2l2ZSBtb2RlbHMgc2lnbmZpY2FudGx5LgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgcGFyYWxsZWwgY29tcHV0aW5nCm5vX2NvcmVzIDwtIGRldGVjdENvcmVzKCkgLSAxICAKY2wgPC0gbWFrZUNsdXN0ZXIobm9fY29yZXMsIHR5cGU9IkZPUksiKQpyZWdpc3RlckRvUGFyYWxsZWwoY2wpICAKYGBgCjxicj48YnI+CgojIyMjIE1vZGVsbGluZwpfX0kgaGF2ZSBvbmx5IHJ1biBhIGZldyBvZiBtb2RlbHMsIHlldC4gVGFrZXMgc28gbXVjaCB0aW1lLi4uX18KX19Ob3RlOiBTYXZlIG1vZGVscyBvbiBnaXRodWIuLi5fXwoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGV2YWw9RkFMU0V9CnN0YXJ0LnRpbWUgPSBTeXMudGltZSgpCgojIHBhcnRpYWwgbGVhc3Qgc3F1YXJlCiAgTS5wbHMgPSB0cmFpbih5PSB5LnRyYWluICwKICAgICAgICAgICAgICB4ID0gZGYudHJhaW4sCiAgICAgICAgICAgICAgbWV0aG9kID0gInBscyIsCiAgICAgICAgICAgICAgdHVuZUxlbmd0aCA9IDIwLAogICAgICAgICAgICAgIHRyQ29udHJvbCA9IGNvbnRyb2xPYmplY3QpCgojIHJpZGdlIHJlZ3Jlc3Npb24gKGVuZXQpICAKICAjIHJpZGdlIGdyaWQKICByaWRnZUdyaWQgPC0gZXhwYW5kLmdyaWQoLmxhbWJkYSA9IGMoMCwgLjAwMSwgLjAxLCAuMSksLmZyYWN0aW9uID0gc2VxKDAuMDUsIDEsIGxlbmd0aCA9IDEwKSkgIAogICMgbW9kZWwKICBNLnJpZGdlICA9IHRyYWluKHk9IHkudHJhaW4gLAogICAgICAgICAgICAgICAgIHggPSBkZi50cmFpbiwKICAgICAgICAgICAgICAgICBtZXRob2QgPSAiZW5ldCIsCiAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSByaWRnZUdyaWQsCiAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY29udHJvbE9iamVjdCkKCiMgbGFzc28gcmVncmVzc2lvbiAoZ2xtbmV0KQogICMgbGFzc28gZ3JpZAogIGxhc3NvR3JpZCA8LSBleHBhbmQuZ3JpZCguYWxwaGEgPSBjKDAsLjIsIC40LCAuNiwgLjgsIDEpLC5sYW1iZGEgPSBzZXEoLjAxLCAyLCBsZW5ndGggPSAyMCkpCiAgIyBNb2RlbAogIE0ubGFzc28gPC0gdHJhaW4oeT0geS50cmFpbiAsCiAgICAgICAgICAgICAgICAgICAgIHggPSBkZi50cmFpbiwKICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImdsbW5ldCIsCiAgICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gInBvaXNzb24iLCAjIGdhdXNzaWFuPyEKICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGxhc3NvR3JpZCwKICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBjb250cm9sT2JqZWN0KQoKIyBtdWx0aXZhcmlhdGUgYWRhcHRpdmUgcmVncmVzc2lvbiBzcGxpbmVzIChlYXJ0aCkKICAjIG1hcnMgZ3JpZAogIG1hcnNHcmlkIDwtIGV4cGFuZC5ncmlkKC5kZWdyZWUgPSAxLCAubnBydW5lID0gMjoyNSkKICAjIE1vZGVsCiAgTS5tYXJzPXRyYWluKHk9IHkudHJhaW4gLAogICAgICAgICAgeCA9IGRmLnRyYWluLAogICAgICAgICAgbWV0aG9kID0gImVhcnRoIiwKICAgICAgICAgIHR1bmVHcmlkID0gbWFyc0dyaWQsCiAgICAgICAgICB0ckNvbnRyb2wgPSBjb250cm9sT2JqZWN0KQogIAogICAgIyB2YXJJbXAoTS5tYXJzKQoKIyByYWRpYWwgc3VwcG9ydCB2ZWN0b3IgbWFjaGluZSAoc3ZtUmFkaWFsKQogICMgcnN2bSBncmlkCiAgcnN2bUdyaWQgPC0gIGV4cGFuZC5ncmlkKHNpZ21hPSAyXmMoLTI1LCAtMjAsIC0xNSwtMTAsIC01LCAwKSwgQz0gMl5jKDA6NCkpCiAgIyBNb2RlbAogIE0ucnN2bSA9IHRyYWluKHk9IHkudHJhaW4gLAogICAgICAgICAgICAgIHggPSBkZi50cmFpbiwKICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUmFkaWFsIiwKICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMjUsCiAgICAgICAgICAgICAgdHVuZUdyaWQgPXJzdm1HcmlkLCAKICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBjb250cm9sT2JqZWN0KQoKIyBTaW5nbGUgcmVncmVzc2lvbiB0cmVlcyAxIChycGFydCkKICBNLnJwYXJ0ID0gdHJhaW4oeT0geS50cmFpbiAsCiAgICAgICAgICAgICAgICB4ID0gZGYudHJhaW4sCiAgICAgICAgICAgICAgICBtZXRob2QgPSAicnBhcnQiLAogICAgICAgICAgICAgICAgdHVuZUxlbmd0aCA9IDIwLAogICAgICAgICAgICAgICAgdHJDb250cm9sID0gY29udHJvbE9iamVjdCkKICAKIyBTaW5nbGUgcmVncmVzc2lvbiB0cmVlcyAyIChjcGFydCkKICBNLmN0cmVlID0gdHJhaW4oeT0geS50cmFpbiAsCiAgICAgICAgICAgICAgICB4ID0gZGYudHJhaW4sCiAgICAgICAgICAgICAgICBtZXRob2QgPSAiY3RyZWUiLAogICAgICAgICAgICAgICAgdHVuZUxlbmd0aCA9IDIwLAogICAgICAgICAgICAgICAgdHJDb250cm9sID0gY29udHJvbE9iamVjdCkKCiMgQmFnZ2VkIHRyZWVzICh0cmVlQmFnKQogIE0uYmFndHJlZSA9IHRyYWluKHk9IHkudHJhaW4gLAogICAgICAgICAgICAgICAgICAgICAgeCA9IGRmLnRyYWluLAogICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInRyZWViYWciLAogICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY29udHJvbE9iamVjdCkKICAKIyBSYW5kb20gZm9yZXN0IChyZikKICBNLnJmID0gdHJhaW4oeT0geS50cmFpbiAsICMjIyB0YWtlcyB0b28gbG9uZyEhIQogICAgICAgICAgICAgIHggPSBkZi50cmFpbiwKICAgICAgICAgICAgICBtZXRob2QgPSAicmYiLAogICAgICAgICAgICAgIHR1bmVMZW5ndGggPSAxMCwKICAgICAgICAgICAgICBudHJlZXMgPSA1MDAsCiAgICAgICAgICAgICAgaW1wb3J0YW5jZSA9IFRSVUUsCiAgICAgICAgICAgICAgdHJDb250cm9sID0gY29udHJvbE9iamVjdCkKICAKIyBCb29zdGVkIHRyZWUgKGdibSkKICAjIGJvb3N0cmVlIGdyaWQKICBib29zdEdyaWQgPSBleHBhbmQuZ3JpZCguaW50ZXJhY3Rpb24uZGVwdGggPSBzZXEoMSwgNywgYnkgPSAyKSwgLm4udHJlZXMgPSBzZXEoMTAwLCAxMDAwLCBieSA9IDUwKSwuc2hyaW5rYWdlID0gYygwLjAxLCAwLjEpLC5uLm1pbm9ic2lubm9kZSA9IGMoMTApKQogICMgTW9kZWwKICBNLmJvb3N0ID0gdHJhaW4oeT0geS50cmFpbiAsCiAgICAgICAgICAgICAgICAgIHggPSBkZi50cmFpbiwKICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImdibSIsCiAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gYm9vc3RHcmlkLAogICAgICAgICAgICAgICAgICB2ZXJib3NlID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGNvbnRyb2xPYmplY3QpCiAgCiMgQ3ViaXN0IChjdWJpc3QpCiAgIyBjdWJpc3QgZ3JpZAogIGN1YmlzdEdyaWQgPC0gZXhwYW5kLmdyaWQoLmNvbW1pdHRlZXMgPSBjKDEsIDI1LCA1MCwgNzUsIDEwMCwxMjUsMTUwLDE3NSwyMDApLC5uZWlnaGJvcnM9YygwLDEsNSw5KSkKICAjIE1vZGVsCiAgTS5jdWJpc3QgPSB0cmFpbih5PSB5LnRyYWluICwKICAgICAgICAgICAgICAgICAgIHggPSBkZi50cmFpbiwKICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJjdWJpc3QiLAogICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBjdWJpc3RHcmlkLAogICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY29udHJvbE9iamVjdCkKICAKIyBuZXVyYWwgbmV0d29yayAoYXZOTmV0KQogICMgbm5ldCBncmlkCiAgbm5ldEdyaWQgPC0gZXhwYW5kLmdyaWQoLmRlY2F5ID0gYygwLjAwMSwgLjAxLCAuMSksLnNpemUgPSBzZXEoMSwgMjcsIGJ5ID0gMiksLmJhZyA9IEZBTFNFKQogICMgbW9kZWwKICBNLm5uZXQgPSB0cmFpbih5PSB5LnRyYWluICwKICAgICAgICAgICAgICAgIHggPSBkZi50cmFpbiwKICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJhdk5OZXQiLAogICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBubmV0R3JpZCwKICAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSAicGNhIiwgIyBubmV0IGFyZSBvZnRlbiBhZmZlY3RlZCBieSBtdWx0aWNvbGluZWFyaXR5CiAgICAgICAgICAgICAgICBsaW5vdXQgPSBUUlVFLAogICAgICAgICAgICAgICAgdHJhY2UgPSBGQUxTRSwKICAgICAgICAgICAgICAgIG1heGl0ID0gNTAwLAogICAgICAgICAgICAgICAgdHJDb250cm9sID0gY29udHJvbE9iamVjdCkKICAKICBmaW5pc2hlZC50aW1lID0gU3lzLnRpbWUoKSAtIHN0YXJ0LnRpbWUKICBjYXQoZmluaXNoZWQudGltZSkKYGBgCgpgYGB7ciBldmFsdWF0aW9ufQptb2RlbHMuZGUgPSBsaXN0KHJlc3VsdC5saXN0ID0gbGlzdChNLnBscyA9IE0ucGxzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNLnJpZGdlID0gTS5yaWRnZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTS5sYXNzbyA9IE0ubGFzc28sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE0ubWFycyA9IE0ubWFycywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTS5yc3ZtID0gTS5yc3ZtLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNLnJwYXJ0ID0gTS5ycGFydCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTS5jdHJlZSA9IE0uY3RyZWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE0uYmFndHJlZSA9IE0uYmFndHJlZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTS5yZiA9IE0ucmYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE0uYm9vc3QgPSBNLmJvb3N0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNLmN1YmlzdCA9IE0uY3ViaXN0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNLm5uZXQgPSBNLm5uZXQpCiAgICAgICAgICAgICAgICAgLCBldmFsLmxpc3QgPSBsaXN0KCkpCgpldmFsLmZ1bmN0aW9uIDwtIGdldFVSTCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Byb2plY3RmbHV0cmVuZC9wZnQuMi9tYXN0ZXIvcXVpY2tmdW5jdGlvbnMvcGZ0X2V2YWxfbW9kZWwiKQpldmFsKHBhcnNlKHRleHQgPSBldmFsLmZ1bmN0aW9uKSkKCmZvcihpIGluIDE6bGVuZ3RoKG1vZGVscy5kZSRyZXN1bHQubGlzdCkpewogIHRyeUNhdGNoKHsKICBtb2RlbHMuZGUkZXZhbC5saXN0W1tpXV0gPSBwZnRfZXZhbF9tb2RlbChtb2RlbHMuZGUkcmVzdWx0Lmxpc3RbW2ldXSkKICAgIG5hbWVzKG1vZGVscy5kZSRldmFsLmxpc3QpW2ldID0gbmFtZXMobW9kZWxzLmRlJHJlc3VsdC5saXN0KVtpXX0sCiAgZXJyb3IgPSBmdW5jdGlvbihlKXtjYXQobmFtZXMobW9kZWxzLmRlJHJlc3VsdC5saXN0KVtpXSAsIjogRXJyb3IgXG4iKX0pCn0KYGBgCjxicj48YnI+CgoqKioqCgojIyMgUmVzdWx0cwoKIyMjIyBDb21wYXJpbmcgbW9kZWwgcmVzdWx0cwoKYGBge3J9CnQgPSAxOyBzdGF0ID0gbGlzdChyc21lPWxpc3QoKSxSc3F1YXJlZD1saXN0KCkscmVzPWxpc3QoKSkKZm9yKHMgaW4gMTpsZW5ndGgobW9kZWxzLmRlJHJlc3VsdC5saXN0KSl7CiAgdHJ5Q2F0Y2goewogICAgaWYoIWlzLm51bGwobW9kZWxzLmRlJHJlc3VsdC5saXN0W1tzXV0pKXsKICAgICAgc3RhdCRyc21lW1t0XV0gPSBtb2RlbHMuZGUkcmVzdWx0Lmxpc3RbW3NdXSRyZXN1bHRzJFJNU0UKICAgICAgbmFtZXMoc3RhdCRyc21lKVt0XSA9IG5hbWVzKG1vZGVscy5kZSRyZXN1bHQubGlzdClbc10KICAgICAgCiAgICAgIHN0YXQkcmVzW1t0XV0gPSBtb2RlbHMuZGUkcmVzdWx0Lmxpc3RbW3NdXQogICAgICBuYW1lcyhzdGF0JHJlcylbdF0gPSBuYW1lcyhtb2RlbHMuZGUkcmVzdWx0Lmxpc3QpW3NdCiAgICAgIHQgPSB0ICsxfQogIH0sCiAgZXJyb3IgPSBmdW5jdGlvbihlKXtjYXQodCAsIjogRXJyb3IgXG4iKX0pCn0KIyBybXNlIGJveC1wbG90LCBzaG93aW5nIGN2IHJlc3VsdHM/IQp0aWNrID0gMTsgcm1zZS5tZWFucyA9IGxpc3QodmFsdWU9TkEsbW9kZWw9TkEpCmZvcihpIGluIDE6bGVuZ3RoKHN0YXQkcnNtZSkpewogIGZvcihsIGluIDE6bGVuZ3RoKHN0YXQkcnNtZVtbaV1dKSl7CiAgICBybXNlLm1lYW5zJHZhbHVlW3RpY2tdID0gc3RhdCRyc21lW1tpXV1bbF0KICAgIHJtc2UubWVhbnMkbW9kZWxbdGlja10gPSBuYW1lcyhzdGF0JHJzbWUpW2ldCiAgICB0aWNrID0gdGljayArMQogIH19CnJtc2UubWVhbnMgPSBkYXRhLmZyYW1lKHJtc2UubWVhbnMpCnJtc2UuYm94cGxvdCA9IAogIGdncGxvdChkYXRhPXJtc2UubWVhbnMgLGFlcyh4PXJlb3JkZXIobW9kZWwsIC1hcy5udW1lcmljKHZhbHVlKSApLHk9dmFsdWUpKSArCiAgZ2VvbV9ib3hwbG90KGZpbGw9ImxpZ2h0Ymx1ZSIpICsKICBjb29yZF9mbGlwKCkgKwogIHhsYWIoIm1vZGVsIikgKwogIHlsYWIoInJtc2UiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoY29sb3VyPSJncmV5MjAiLHNpemU9MjApKSAKCnJtc2UuYm94cGxvdCArCiAgeWxpbSgxLDMuNSkKYGBgCgoKIyMjIyBFdmFsdWF0aW5nIHRoZSBtb2RlbHMgaW5kaXZpZHVhbGx5CgpIb3cgeW91IHNob3VsZCByZWFkIHRoZSBmaWd1cmUgYmVsb3c6CgpFYWNoIHJvdyByZXByZXNlbnRzIG9uZSB0eXBlIG9mIG1vZGVsLiBPbiB0aGUgbGVmdCBzaWRlLCB5b3Ugc2VlIHRoZSByZXN1bHRzIGZyb20gdGhlIG1vZGVsIHR1bmluZy9jcm9zcyB2YWxpZGF0aW9uLiBUaGUgcm1zZSByZWZlcnMgdG8gdGhlIG1lYW4gZXJyb3Igd2l0aGluIHRoZSB2YWxpZGF0aW9uIHNldC4gVGhlIHR1bmluZyBwYXJhbWV0ZXJzIHdpdGggdGhlIGxvd2VzdCBybXNlIGFyZSBzZWxlY3RlZCBmb3IgdGhlIGZpbmFsIG1vZGVsLiBPbiB0aGUgcmlnaHQgc2lkZSwgeW91IHNlZSB0aGUgY29tcGFyaXNvbiBiZXR3ZWVuIHByZWRpY3RlZCBhbmQgb2JzZXJ2ZXJkIHZhbHVlcywgb2YgdGhlIGZpbmFsIG1vZGVsLCB3aXRoaW4gdGhlIGNvbXBsZXRlIHRyYWluaW5nIGRhdGEgc2V0IGFuZCB0aGUgdGVzdCBkYXRhIHNldC4gV2UgZXhwZWN0IHRvIHNlZSBhIGxvd2VyIGVycm9yIHdpdGhpbiB0aGUgdHJhaW5pbmcgZGF0YSBzZXQgKHJpZ2h0IHBsb3QsIG9yYW5nZSksIHRoYW4gaW4gdGhlIHRlc3QgZGF0YSBzZXQgKHJpZ2h0IHBsb3QsIHJlZCkgb3IgdGhlIHZhbGlkYXRpb24gc2V0IChsZWZ0IHNpZGUpLiBGdXJ0aGVybW9yZSwgdGhlIG1lYW4gcm1zZSB3aXRoaW4gdGhlIHZhbGlkYXRpb24gc2V0cyBzaG91bGQgYmUgY29tcGFyYWJsZSB0byB0aGUgcm1zZSB3aXRoaW4gdGhlIHRlc3QgZGF0YSBzZXQuIFRoZSBtb3N0IGltcG9ydGFudCAKCmBgYHtyLCBmaWcud2lkdGg9NyxmaWcuaGVpZ2h0PTIxfQoKZ3JpZC5hcnJhbmdlKHBsb3QobW9kZWxzLmRlJHJlc3VsdC5saXN0W1sxXV0gKSwKICAgICAgICAgICAgIG1vZGVscy5kZSRldmFsLmxpc3RbWzFdXSRwbG90cyRwcmVkLnBsb3QsCiAgICAgICAgICAgICBwbG90KG1vZGVscy5kZSRyZXN1bHQubGlzdFtbMl1dICksCiAgICAgICAgICAgICBtb2RlbHMuZGUkZXZhbC5saXN0W1syXV0kcGxvdHMkcHJlZC5wbG90LAogICAgICAgICAgICAgcGxvdChtb2RlbHMuZGUkcmVzdWx0Lmxpc3RbWzNdXSApLAogICAgICAgICAgICAgbW9kZWxzLmRlJGV2YWwubGlzdFtbM11dJHBsb3RzJHByZWQucGxvdCwKICAgICAgICAgICAgIHBsb3QobW9kZWxzLmRlJHJlc3VsdC5saXN0W1s0XV0gKSwKICAgICAgICAgICAgIG1vZGVscy5kZSRldmFsLmxpc3RbWzRdXSRwbG90cyRwcmVkLnBsb3QsCiAgICAgICAgICAgICBwbG90KG1vZGVscy5kZSRyZXN1bHQubGlzdFtbNV1dICksCiAgICAgICAgICAgICBtb2RlbHMuZGUkZXZhbC5saXN0W1s1XV0kcGxvdHMkcHJlZC5wbG90LAogICAgICAgICAgICAgcGxvdChtb2RlbHMuZGUkcmVzdWx0Lmxpc3RbWzZdXSApLAogICAgICAgICAgICAgbW9kZWxzLmRlJGV2YWwubGlzdFtbNl1dJHBsb3RzJHByZWQucGxvdCwKICAgICAgICAgICAgIHBsb3QobW9kZWxzLmRlJHJlc3VsdC5saXN0W1s3XV0gKSwKICAgICAgICAgICAgIG1vZGVscy5kZSRldmFsLmxpc3RbWzddXSRwbG90cyRwcmVkLnBsb3QsCiAgICAgICAgICAgICMgOCBpcyBtaXNzaW5nISEhCiAgICAgICAgICAgICAKICAgICAgICAgICAgIHBsb3QobW9kZWxzLmRlJHJlc3VsdC5saXN0W1s5XV0gKSwKICAgICAgICAgICAgIG1vZGVscy5kZSRldmFsLmxpc3RbWzldXSRwbG90cyRwcmVkLnBsb3QsCiAgICAgICAgICAgICBwbG90KG1vZGVscy5kZSRyZXN1bHQubGlzdFtbMTBdXSApLAogICAgICAgICAgICAgbW9kZWxzLmRlJGV2YWwubGlzdFtbMTBdXSRwbG90cyRwcmVkLnBsb3QsCiAgICAgICAgICAgICBwbG90KG1vZGVscy5kZSRyZXN1bHQubGlzdFtbMTFdXSApLAogICAgICAgICAgICAgbW9kZWxzLmRlJGV2YWwubGlzdFtbMTFdXSRwbG90cyRwcmVkLnBsb3QsCiAgICAgICAgICAgICBwbG90KG1vZGVscy5kZSRyZXN1bHQubGlzdFtbMTJdXSApLAogICAgICAgICAgICAgbW9kZWxzLmRlJGV2YWwubGlzdFtbMTJdXSRwbG90cyRwcmVkLnBsb3QsCiAgICAgICAgICAgICB3aWR0aHM9MjozLCBuY29sPTIpCgpgYGAKCmBgYApOT1QgSU5DTFVERUQsIFlFVDoKCiAgICAgICAgICAgICAgICAgICAgICAgICBwbG90KG1vZGVscy5kZSRyZXN1bHQubGlzdFtbNF1dICksCiAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbHMuZGUkZXZhbC5saXN0W1s0XV0kcGxvdHMkcHJlZC5wbG90LAogICAgICAgICAgICAgICAgICAgICAgICAgcGxvdChtb2RlbHMuZGUkcmVzdWx0Lmxpc3RbWzVdXSApLAogICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxzLmRlJGV2YWwubGlzdFtbNV1dJHBsb3RzJHByZWQucGxvdCwKICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3QobW9kZWxzLmRlJHJlc3VsdC5saXN0W1s2XV0gKSwKICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVscy5kZSRldmFsLmxpc3RbWzZdXSRwbG90cyRwcmVkLnBsb3QsCiAgICAgICAgICAgICAgICAgICAgICAgICBwbG90KG1vZGVscy5kZSRyZXN1bHQubGlzdFtbN11dICksCiAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbHMuZGUkZXZhbC5saXN0W1s3XV0kcGxvdHMkcHJlZC5wbG90LApgYGAKCiMjIyMgTW9kZWwgc2VsZWN0aW9uCgojIyMjIE5vd2Nhc3QgbW9kZWwgdmVyc3UgRm9yZWNhc3QgbW9kZWwKCiMjIyMgSW5mbHVlbnphLU5vd2Nhc3RpbmcKCioqKioKCiMjIyBEaXNjdXNzaW9uIAoKQ2hhbmdlIHBvaW50IGFuYWx5c2lzCgptb3JlIGRhdGEgc291cmNlczogdHdpdHRlciwgZXRjIGV0YwoKdHJhbnNmZXIgdGhlIGFwcHJvYWNoIHRvIG90aGVyIHNldHRpbmdzIGFuZCBzZWUgd2hhdCB0aGUgcmVzdWx0cyBhcmUuIFRvIHRoZSBiZXN0IG9mIG91ciBrbm93bGVkZ2UsIGRpZ2l0YWwgZXBpZGVtaW9sb2d5IGhhcyBub3QgYmVlbiB0cmllZCBpbiBtb3N0IGNvdW50cmllcyBhcm91bmQgdGhlIHdvcmxkLiBSZXN1bHRzIG1pZ2h0IGRpZmZlciBjb25zaWRlcmFibHksIGRlcGVuZGluZyBvbiB0aGUgZGVncmVlIG9mIGRpZ2l0YWxpemF0aW9uLCBpbmZvcm1hdGlvbiBzZWVraW5nIGJlaGF2aW91ciwgdGhlIHNvdXJjZXMgb2YgaW5mb3JtYXRpb24sIGV0YyBldGMuIAoKcmVwb3J0aW5nCgoKClRleHQKCi4uLnJlbmRlcigiaW5wdXQuUm1kIiwgaHRtbF9kb2N1bWVudChjb2RlX2Rvd25sb2FkPVRSVUUpKQ==